findLoadedClass()返回null

mus*_*iKk 8 java jvm internals

根据JVM规范,启动类加载类加载器由JVM 记录为启动类加载器.而且,根据ClassLoader的JavaDoc #findLoadedClass()方法

如果Java虚拟机已将此加载器记录为具有该二进制名称的类的初始加载器,返回具有给定二进制名称的类.

(强调我的)

考虑一个简单的类加载器

class SimpleClassLoader extends ClassLoader {
    void foo() {
        System.err.println(loadClass("foo.Bar"));
        System.err.println(findLoadedClass("foo.Bar"));
    }
}
Run Code Online (Sandbox Code Playgroud)

鉴于foo.Bar实际存在于类路径中,new SimpleClassLoader().foo()打印

class foo.Bar
null
Run Code Online (Sandbox Code Playgroud)

根据上面给出的原因,SimpleClassLoader应该是启动类加载器并且findLoadedClass("foo.Bar")应该只返回成功加载的类.

现在考虑第二个版本:

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"));
    }
}
Run Code Online (Sandbox Code Playgroud)

这使得SimpleClassLoader2启动以及定义类加载器都成为了foo.Bar.确实,现在new SimpleClassLoader2().foo()打印出所需的

class foo.Bar
class foo.Bar
Run Code Online (Sandbox Code Playgroud)

所以要么文档是错误的,要么我不明白为什么SimpleClassLoader不被视为启动类加载器foo.Bar.有人可以对此有所了解吗?

mus*_*iKk 3

我做了更多测试,我相当确定规范已正确实现。我的错误是认为反射加载类与将其作为解析步骤的一部分加载是一样的。这是有道理的:规范和 JavaDoc 都提到“记录”类加载器作为启动类加载器。如果我调用loadClass()自己,VM 无法知道哪个类加载器应该是启动类加载器,因此定义类加载器也很容易成为启动类加载器。

这可以通过让已加载的类触发另一个类 ( foo.Baz) 的加载作为依赖关系解析的一部分来演示,但让另一个类加载器执行实际加载。*

*我很确定这不是有效类加载器的正确行为。我这样做只是为了说明一点。

考虑以下类(它们都在 package 中foo):

public class Bar {
    public Bar() {
        new Baz();
    }
}
Run Code Online (Sandbox Code Playgroud)

public class Baz {
}
Run Code Online (Sandbox Code Playgroud)

我的自定义类加载器现在稍作修改:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

测试很简单:

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");
}
Run Code Online (Sandbox Code Playgroud)

输出是

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
Run Code Online (Sandbox Code Playgroud)

可以看出:SimpleClassLoader启动 的加载并定义foo.Bar. 创建实例会触发 的解析foo.Baz。这次,类的定义被委托给系统类加载器,因此它成为定义类加载器。输出显示正在SimpleClassLoader为两个类启动类加载器,但仅定义第一个类。