Java 在JVM中加载类时

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!"); } } 编译所有三个类。删除一个类。干管。没有抛出异常 例

我举了两个例子:

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

}
编译所有三个类。删除一个类。干管。没有抛出异常

例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
)。