Java 在JVM中加载类时
我举了两个例子: 例1:Java 在JVM中加载类时,java,classloader,Java,Classloader,我举了两个例子: 例1: public class A { } public class B { public void m(A a) { } } public class C { public static void main(String[] args) { B b = new B(); System.out.println("hello!"); } } 编译所有三个类。删除一个类。干管。没有抛出异常 例
public class A {
}
public class B {
public void m(A a) {
}
}
public class C {
public static void main(String[] args) {
B b = new B();
System.out.println("hello!");
}
}
编译所有三个类。删除一个类。干管。没有抛出异常
例2:
public class D {
}
public class E {
public void omg(D d) {
}
public static void main(String[] args) {
E e = new E();
}
}
编译类。删除D类。运行main方法
Exception in thread "main" java.lang.NoClassDefFoundError: D
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at sun.launcher.LauncherHelper.getMainMethod(Unknown Source)
at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
Caused by: java.lang.ClassNotFoundException: D
at java.net.URLClassLoader$1.run(Unknown Source)
为什么??D永远不会被引用 这两者都是JavaVM规范所允许的。在这方面,我们有: 例如,Java虚拟机实现可以选择在使用类或接口时单独解析每个符号引用(“延迟”或“延迟”解析),或者在验证类时一次性解析所有符号引用(“急切”或“静态”解析)
我的猜测是Sun/Oracle选择对初始(“main”)类进行“静态”解析,因为很可能很快就会调用主类中的方法。您的类在方法
public void omg(D)
中引用了类D
通常,Sun/Oracle的JVM使用延迟解析,因此只要不使用该方法就无所谓了,但是,从堆栈跟踪中可以看出,主方法是通过反射操作Class.getMethod
搜索的,这会产生不同
您可以使用以下代码对此进行验证:
public class B {
public static void main(String[] args) {
D.main(args);
}
}
class D {
public void foo(E e) {}
public static void main(String[] args) {
System.out.println("hello world");
}
}
class E { }
在这里,编译并运行javab
后删除E.class
不会产生错误。现在在代码中插入反射方法查找:
public class B {
public static void main(String[] args) {
try {
D.class.getMethod("main", String[].class);
} catch(NoSuchMethodException ex) {
ex.printStackTrace();
}
D.main(args);
}
}
class D {
public void foo(E e) {}
public static void main(String[] args) {
System.out.println("hello world");
}
}
class E { }
现在,在编译后删除类E
会在使用javab
运行时产生java.lang.NoClassDefFoundError:E
。因此,手动触发的方法查找再现了原始代码示例中的行为,尽管这里的类D
不是main
类
请注意,可以通过从方法
foo
中删除public
修饰符来修复此问题。原因是Class.getMethod
只考虑public
方法,而跳过所有其他方法
这也适用于您的原始代码示例:从方法
omg
中删除public
将使NoClassDefFoundError
消失。好问题!我想查看javap
的输出将是了解实际编译内容的一个良好开端,以确保编译器没有优化某些调用。这可能包含以下答案:该类未被使用,但被引用。您正在将其用作方法参数。该类将被扫描它需要的所有类型(基本上是所有导入),这些类型将被加载到类加载器中。您可以通过使用-verbose:class
参数启动JVM来查看加载类的时间。@M.Deinum,在第一个示例中,stacktrace没有说明一切吗?sun launcher的.getMainMethod
查找所有方法签名。在第一个示例中,您只在类C
上调用它,而在类B
上调用它,正如答案中指出的那样,直接引用被急切地加载,而间接引用被延迟(或延迟)加载。因此,您的B类已加载但未验证,如果您尝试调用方法m
,它将开始失败,因为现在将检查类B
,并加载其依赖项。否则,它将在调用该类的第一个方法后开始解析。否,解析行为与所有其他类相同。请看…非常有趣的示例,我尝试了MethodHandles.Lookup::findStatic
而不是Class::getMethod
来获得不同的result@Eugene我认为,MethodHandles.Lookup::findStatic
的工作原理与JVM在第一次执行invokestatic
指令时的内部工作原理更接近,而Class::getMethod
将触发一个Method[]
数组的初始化,该数组包含该类的所有public
方法(如果尚未存在),该数组将被重新用于后续搜索(以及仅克隆该数组的Class::getMethods
)。