Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

findLoadedClass() returns null

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?

like image 514
musiKk Avatar asked Feb 23 '14 00:02

musiKk


1 Answers

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.

like image 138
musiKk Avatar answered Oct 14 '22 23:10

musiKk