Java JVM类加载中的意外行为(真正需要类之前的ClassNotFoundException)

Java JVM类加载中的意外行为(真正需要类之前的ClassNotFoundException),java,classloader,dynamic-class-loaders,Java,Classloader,Dynamic Class Loaders,我需要帮助才能理解为什么会发生这种情况: 使用Java 1.8.0131,我有这样一个类: 公共类动态类加载应用程序{ /* *此版本不起作用,在执行main()之前会引发ClassNotFoundException */ //如果这个方法从libtwo接收到childclasses,一切都会正常工作! 私有静态void showMessage(来自libone obj的最终父类){ System.out.println(obj.message()); } publicstaticvoidmai

我需要帮助才能理解为什么会发生这种情况:

使用Java 1.8.0131,我有这样一个类:

公共类动态类加载应用程序{
/*
*此版本不起作用,在执行main()之前会引发ClassNotFoundException
*/
//如果这个方法从libtwo接收到childclasses,一切都会正常工作!
私有静态void showMessage(来自libone obj的最终父类){
System.out.println(obj.message());
}
publicstaticvoidmain(最终字符串[]args)抛出Throwable{
试一试{
final ChildClassFromLibTwo obj=新的ChildClassFromLibTwo();
showMessage(obj);
}捕获(忽略最终丢弃){
//忽略,我们只想在它存在时使用它
}
System.out.println(“应该显示,但是没有:(”);
}
}
另外两个类正在使用:
ParentClassFromLibOne
ChildClassFromLibTwo
。后者是对前者的扩展

涉及两个外部库:

  • 一个库称为
    libone
    ,包含
    ParentClassFromLibOne
    类。应用程序在类路径中包含此库,用于编译和执行
  • 第二个库称为
    libtwo
    ,包含
    childclassfromtlibtwo
    类。应用程序在类路径中包含此库,用于编译,但不用于执行
据我所知,Java运行时应尝试在以下行加载
ChildClassFromLibTwo
(运行时类路径中的不是):

final ChildClassFromLibTwo obj=new ChildClassFromLibTwo();
如果此类不在类路径中,则应抛出
ClassNotFoundException
,如果此行位于
try…catch(Throwable)
中,则应执行末尾的
System.out.println

但是,我得到的是在加载
DynamicClassLoadingAppKO
本身时抛出的
ClassNotFoundException
,显然是在执行
main()
方法之前抛出的
try…catch

对我来说更奇怪的是,如果我更改
showMessage()
方法的签名,使其不是接收父类的参数,而是直接接收子类的参数,那么这种行为就会消失,一切都会按照我的预期工作:

/*
*此版本工作正常,因为showMessage将子类作为参数接收
*/
私有静态void showMessage(来自libtwo obj的最终子类){
System.out.println(obj.message());
}
这怎么可能?在类加载的工作方式中,我遗漏了什么

为了方便测试,我创建了一个GitHub存储库,复制这种行为


[1]

这比您想象的要复杂一些。加载类时,将验证所有函数。在验证阶段,还将加载所有引用的类,因为需要它们来计算字节码中任何给定位置堆栈上的确切类型


如果您想要这种懒惰行为,您必须将-noverify选项传递给Java,这将延迟类的加载,直到引用它们的函数第一次执行为止。但是,出于安全原因,当您无法完全控制将加载到JVM中的类时,不要使用
-noverify

好的安迪·威尔金森(Andy Wilkinson)及时指出了这一点,我很幸运地在这张春季入场券[1]中解释了为什么会发生这种情况。在我看来,这绝对是一个困难的问题

显然,在这种情况下发生的情况是,当调用类本身被加载时,验证器开始工作,并看到
showMessage()
方法收到一个类型为
ParentClassFromLibOne
的参数。到目前为止还不错,即使运行时
ParentClassFromLibOne
不在类路径中,这也不会在这个阶段引发
ClassNotFoundException

但是显然,验证器也扫描方法代码,并注意到
main()
中有一个对该
showMessage()
方法的调用。该调用不是作为参数传递的
ParentClassFromLibOne
,而是另一个类的对象:
ChildClassFromLibTwo

因此,在这种情况下,验证器确实尝试加载
ChildClassFromLibTwo
,以便能够检查它是否真的从
ParentClassFromLibOne
扩展

有趣的是,如果
ParentClassFromLibOne
是一个接口,则不会发生这种情况,因为接口在赋值时被视为
对象

另外,如果
showMessage(…)
直接请求一个
ChildClassFromLibTwo
作为参数,则不会发生这种情况,因为在这种情况下,验证器不需要加载子类来检查它是否与自身兼容

丹尼尔,我投票赞成你的答案,但我不认为它是被接受的,因为我认为它无法解释为什么在验证时间发生这种情况(它不是导致该代码的类,导致<代码> CaseNoxFuffestExp><代码> >的类别。


[1]

在执行之前,您必须能够编译。如果找不到类:它无法编译。如上所述,“找不到”类在编译时在类路径中可用,但在运行时不可用。如果类在编译时存在,但在运行时不存在,则会得到一个
NoClassDefFoundError
,而不是
ClassNotFoundException
。如果没有有效的