java - findLoadedClass() 返回 null

标签 java jvm internals

According to the JVM spec ,启动类加载的类加载器被 JVM 记录为启动类加载器。此外,根据 ClassLoader#findLoadedClass() 的 JavaDoc方法

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.

(强调我的)

考虑一个简单的类加载器

class SimpleClassLoader extends ClassLoader {
    void foo() {
        System.err.println(loadClass("foo.Bar"));
        System.err.println(findLoadedClass("foo.Bar"));
    }
}

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

class foo.Bar
null

根据上面给出的原因,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"));
    }
}

这使得 SimpleClassLoader2 成为 foo.Bar 的启动类加载器和定义类加载器。事实上,现在 new SimpleClassLoader2().foo() 打印了所需的

class foo.Bar
class foo.Bar

所以要么文档有误,要么我不明白为什么 SimpleClassLoader 不被视为 foo.Bar 的初始类加载器。有人可以解释一下吗?

最佳答案

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

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

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

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

public class Bar {
    public Bar() {
        new Baz();
    }
}

public class Baz {
}

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

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();
        }
    }
}

测试很简单:

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");
}

输出是

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

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

关于java - findLoadedClass() 返回 null,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21962631/

相关文章:

java - eclipse 在哪里保存类路径变量值?

java - 如何在主线程中保持 apache Camel 上下文处于 Activity 状态

c++ - map 是否将元素存储为 std::pair?

java - Netty 服务器如何向不同的客户端发送数据?

java - 如何优化这个字符串搜索/替换方法?

java - jvm 次要版本与编译器次要版本

java - JVM线程调度算法是什么?

java - 每个方法的操作数栈 V/S 每个方法的局部变量

exception - OCaml 内部结构 : Exceptions