Lets see this code to understand how classloader leaks possible
Main.java
public class Main {
public static void main(String...args) throws Exception {
List<Object> list = new ArrayList<>();
loadClass(list);
while (true) {
System.gc();
Thread.sleep(1000);
}
}
private static void loadClass(List list) throws Exception {
URL url = Main.class.getProtectionDomain().getCodeSource().getLocation();
MyCustomClassLoader cl = new MyCustomClassLoader(url);
Class<?> clazz = cl.loadClass("com.test.Foo");
list.add(clazz.newInstance());
cl = null;
}
}
class MyCustomClassLoader extends URLClassLoader {
public MyCustomClassLoader(URL... urls) {
super(urls, null);
}
@Override
protected void finalize() {
System.out.println("*** CustomClassLoader finalized!");
}
}
Foo.java
public class Foo {
public Foo() {
System.out.println("Test ClassLoader: " + this.getClass().getClassLoader());
}
@Override
protected void finalize() {
System.out.println( this + " finalized!");
}
}
The output of this as follows:
Test ClassLoader: com.test.MyCustomClassLoader@71dac704
So, here we can see "*** CustomClassLoader finalized!" is not called and this is because MyCustomClassLoader is holding a reference of object list as the instances loaded by classloader are kept in it.
Now, lets change the code a bit, so here we will set list to null
public static void main(String...args) throws Exception {
List<Object> list = new ArrayList<>();
loadClass(list);
while (true) {
System.gc();
Thread.sleep(1000);
list = null;
}
}
And now see the output
Test ClassLoader: com.test.MyCustomClassLoader@71dac704
com.test.Foo@650de12 finalized!
*** CustomClassLoader finalized!