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/