为啥静态字段初始化失败导致NoClassDefFoundError?

Posted

技术标签:

【中文标题】为啥静态字段初始化失败导致NoClassDefFoundError?【英文标题】:Why NoClassDefFoundError caused by static field initialization failure?为什么静态字段初始化失败导致NoClassDefFoundError? 【发布时间】:2011-09-15 04:25:04 【问题描述】:

这是一个有趣的 java 问题。

以下简单的 java 程序包含由方法静态初始化的静态字段。实际上,我强制计算初始化值的方法引发 NullPointException,当我访问这样的静态字段时,会引发 NoClassDefFoundError。似乎VM对待Class不完整。

但是当我访问 Class 时,它仍然可用;

有人知道为什么吗?

class TestClass 
    public static TestClass instance = init();

    public static TestClass init() 
       String a = null;
       a.charAt(0); //force a null point exception;
       return new TestClass();
    


class MainClass 
    static public void main(String[] args) 
       accessStatic(); // a ExceptionInInitializerError raised cause by NullPointer
       accessStatic(); //now a NoClassDefFoundError occurs;

       // But the class of TestClass is still available; why?
       System.out.println("TestClass.class=" + TestClass.class);
    

    static void accessStatic() 
        TestClass a;

        try 
            a = TestClass.instance; 
         catch(Throwable e) 
            e.printStackTrace();
        
       

【问题讨论】:

这也让我感到惊讶!我联系了一个可能知道答案的朋友;我们会看看他是否回复。 我认为是因为静态初始化程序中的异常,类加载器无法加载TestClass @Arun:那部分是微不足道的;我认为他甚至没有问过这个问题。令人惊讶的部分是第三个打印输出,它显示MainClass 中的TestClass.class 实际上包含对物理Class 对象的引用。如果类没有正确初始化,为什么我们还可以触摸它? @Ernest TestClass.class 似乎不会触发类加载,并且看起来它是可访问/可用的,与是否已加载类无关。 【参考方案1】:

这些问题的答案通常隐藏在规范中的某个地方...(§12.4.2)

初始化类时会发生什么:

步骤 1-4 与此问题有些无关。第 5 步是触发异常的原因:

5如果 Class 对象处于错误状态,则无法进行初始化。释放对 Class 对象的锁定并抛出 NoClassDefFoundError。

6-8继续初始化,8执行初始化器,一般发生在第9步:

9。如果初始化器的执行正常完成,则锁定这个Class对象,标记它完全初始化,通知所有等待的线程,释放锁,正常完成这个过程。

但是我们在初始化程序中遇到了一个错误,所以:

10否则,初始化程序必须通过抛出一些异常 E 突然完成。 如果 E 的类不是 Error 或其子类之一,则 使用 E 创建 ExceptionInInitializerError 类的新实例作为参数, 并在接下来的步骤中使用此对象代替 E。但是,如果由于发生 OutOfMemoryError 而无法创建新的 ExceptionInInitializerError 实例,则在以下步骤中使用 OutOfMemoryError 对象代替 E。

是的,我们看到了空指针异常的ExceptionInInitializerError b/c。

11。锁定 Class 对象,将其标记为错误,通知所有等待的线程,释放锁定,并使用上一步确定的原因 E 或替换原因突然完成此过程。 (由于一些早期实现中的缺陷,类初始化期间的异常被忽略,而不是导致此处描述的 ExceptionInInitializerError。)

然后该类被标记为错误,这就是我们第二次从第 5 步得到异常的原因。


令人惊讶的部分是第三个打印输出,它显示MainClass 中的TestClass.class 实际上包含对物理Class 对象的引用。

可能是因为TestClass 仍然存在,它只是被标记为错误的。它已经加载并验证了。

【讨论】:

太棒了。这是名称“NoClassDefFoundError”的问题,必须阅读规范才能理解它。名称和 javadoc 都没有解释它是什么。因为这么多年java大佬都不明白,可以说这个名字彻底失败了。 "(由于一些早期实现中的缺陷,类初始化期间的异常被忽略,而不是导致此处描述的 ExceptionInInitializerError。)" - 在哪些 Java 版本中存在此缺陷?我在 Java 1.8.0_25-b17 中遇到过这种情况。在类初始化时抛出了 NoClassDefFound 错误,但原来的异常被丢弃了(给我造成了很多小时的压力,试图找出我的类路径出了什么问题)。 @irreputable 有 JDK-6766753 建议引入子类或改进文档,以便在可能抛出 NoClassDefFoundError 时更清楚(尽管该报告自 2008 年以来一直开放)。【参考方案2】:

是的,这通常是引发NoClassDefFoundError 的原因。它的名字邪恶,仅此而已。它应该被命名为“类初始化失败异常”之类的。

由于这个误导性的名称,遇到此错误的 Java 程序员浪费了数百年的时间试图弄清楚为什么找不到该类。

每当你看到这个异常时,你应该向上检查日志,并尝试找出类初始化失败的根本原因。

【讨论】:

这个类的名字很糟糕,但问题主要是程序员造成的。他的应用程序不应该尝试NoClassDefFoundError第一次提出时恢复。 我不觉得它的名字不好。在类初始化失败期间抛出异常最多是一个糟糕的选择。 NoClassDefFoundError 当然在其他情况下也有它的用处(例如,在运行时类路径中找不到类定义,但确定它在编译时类路径中可用)。 @Stephen 这对程序员不公平。在所有应用程序中,类都由多个线程访问。如果一个类初始化失败,第一个访问它的线程会得到一个初始化错误,其他访问它的线程会得到一个类 def not found 错误。该错误不仅名称不佳。静态初始化失败是非常常见的,它应该有一个不言自明的名字。 “class def”,不管它是什么,(注意 def 的不寻常缩写),是一个普通 Java 程序员不知道的行话,但它的名字如此邪恶,以至于向人们传达了错误的信息。一个完全神秘的名字会比这个更好。 @irreputable - 出现此错误的第一个线程应该拔掉整个应用程序的插头,而不是静静地死去。安装一个默认的未捕获异常处理程序来执行此操作。 到目前为止,NoClassDefFoundError 最大的缺点是它不需要 cause。这一定会导致数百万甚至数十亿美元的生产力损失。一个非常愚蠢的决定。【参考方案3】:

当我访问这样一个静态字段时,会引发 NoClassDefFoundError。看来VM对待Class不完整。

没错……

但是当我访问 Class 时,它仍然可用

是的。

类加载器没有尝试删除损坏的类,因为:

很难做到, 非常很难做到安全, 这会使 JVM 处于一种应用程序很容易浪费大量时间重复加载和重新加载损坏代码的状态,并且 规范说(或至少暗示)它不应该;有关详细信息,请参阅其他答案。

要进入这种不一致可见的状态,您的应用程序必须捕获ClassDefNotFoundError(或超类)并尝试从中恢复。 Error 异常通常是不可恢复的,这是一个有据可查的事实。即,如果您尝试恢复,JVM 可能最终处于不一致的状态。这就是这里发生的事情......关于正在加载/初始化的类。

【讨论】:

【参考方案4】:

有限制

8.3.2.2 的 http://psc.informatik.uni-jena.de/languages/Java/javaspec-3.pdf

【讨论】:

以上是关于为啥静态字段初始化失败导致NoClassDefFoundError?的主要内容,如果未能解决你的问题,请参考以下文章

为啥静态字段没有及时初始化?

为啥我可以通过反射在字段初始化后重写静态只读字段?

为啥我无法在 C++ 中初始化静态字段 [重复]

MSVC 导致静态 const 模板成员初始化失败

java调用同一个类中的方法为啥要将方法申明成静态?

为啥arcGIS打开的时候,一直是正在初始化应用程序