According to the JVM spec, the class loader that initiates loading of a class is recorded as the initiating class loader by the JVM. Furthermore, according to the JavaDoc of ClassLoader#findLoadedClass() the method
Returns the class with the given binary name if this loader has been recorded by the Java virtual machine as an initiating loader of a class with that binary name.
(emphasis mine)
Consider a simple class loader
class SimpleClassLoader extends ClassLoader {
void foo() {
System.err.println(loadClass("foo.Bar"));
System.err.println(findLoadedClass("foo.Bar"));
}
}
Given that foo.Bar
actually exists in the class path, new SimpleClassLoader().foo()
prints
class foo.Bar
null
According to the reasons given above, SimpleClassLoader
should be the initiating class loader and findLoadedClass("foo.Bar")
should just return the successfully loaded class.
Now consider this second version:
class SimpleClassLoader2 extends ClassLoader {
SimpleClassLoader2() {
super(null); // disables delegation
}
protected Class<?> findClass(String name) {
try {
byte[] b = IOUtils.toByteArray(new FileInputStream("path/to/foo/Bar.class"));
return defineClass("foo.Bar", b, 0, b.length);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
void foo() {
System.err.println(loadClass("foo.Bar"));
System.err.println(findLoadedClass("foo.Bar"));
}
}
This makes SimpleClassLoader2
both the initiating as well as the defining class loader of foo.Bar
. Indeed, now new SimpleClassLoader2().foo()
prints the desired
class foo.Bar
class foo.Bar
So either the documentation is wrong or I don't understand why SimpleClassLoader
is not regarded as the initiating class loader of foo.Bar
. Can someone please shed some light into this?
I did some more tests and I'm fairly sure the spec is correctly implemented. My mistake was thinking that reflectively loading a class is the same as having it load as part of the resolution step. It makes sense: Both the spec and the JavaDoc mention "recording" of a class loader as the initiating class loader. If I call loadClass()
myself, the VM has no way of knowing what class loader should be the initiating class loader, so the defining class loader trivially becomes the initiating class loader as well.
This can be demonstrated by having the loaded class trigger loading of another class (foo.Baz
) as part of dependency resolution but have another class loader do the actual loading.*
*I'm pretty sure this is not correct behavior of a valid class loader. I just do it to illustrate a point.
Consider the following classes (they are all in package foo
):
public class Bar {
public Bar() {
new Baz();
}
}
and
public class Baz {
}
My custom class loader is now slightly modified:
public class SimpleClassLoader extends ClassLoader {
static final String PATH = "/path/to/classes";
public SimpleClassLoader() {
// disable parent delegation
super(null);
}
public void printLoadedClass(String name) throws Exception {
Class<?> cls = findLoadedClass(name);
System.err.println("findLoadedClass(" + name + ") = " + cls
+ ", has class loader " + cls.getClassLoader());
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (name.equals("foo.Baz")) {
// don't want to be defining class loader of foo.Baz
return getSystemClassLoader().loadClass(name);
}
// now we're loading foo.Bar
try {
byte[] b = IOUtils.toByteArray(new FileInputStream(PATH + "/foo/Bar.class"));
return defineClass(name, b, 0, b.length);
} catch (ClassFormatError | IOException e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
The test is straight forward:
public static void main(String[] args) throws Exception {
SimpleClassLoader cl = new SimpleClassLoader();
Class<?> cls = cl.loadClass("foo.Bar");
cls.newInstance(); // this triggers resolution
cl.printLoadedClass("foo.Bar");
cl.printLoadedClass("foo.Baz");
}
Output is
findLoadedClass(foo.Bar) = class foo.Bar, has class loader foo.SimpleClassLoader@3a65724d
findLoadedClass(foo.Baz) = class foo.Baz, has class loader sun.misc.Launcher$AppClassLoader@1a2b2cf8
As can be seen: SimpleClassLoader
initiates loading of and also defines foo.Bar
. Creating the instance triggers resolution of foo.Baz
. This time, definition of the class is delegated to the
system class loader so it becomes the defining class loader. The output shows that SimpleClassLoader
is initiating class loader for both classes but defines only the first class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With