【问题标题】:How to analyse a NoClassDefFoundError caused by an ignored ExceptionInInitializerError?如何分析由忽略的 ExceptionInInitializerError 引起的 NoClassDefFoundError?
【发布时间】:2011-01-13 17:18:00
【问题描述】:

今天我花了一个下午分析 NoClassDefFoundError。一次又一次地验证classpath,结果发现有一个类的静态成员抛出了第一次被忽略的异常。之后,每次使用该类都会抛出一个没有有意义的堆栈跟踪的 NoClassDefFoundError:

Exception in thread "main" java.lang.NoClassDefFoundError: 
    Could not initialize class InitializationProblem$A
    at InitializationProblem.main(InitializationProblem.java:19)

就是这样。没有更多的行了。

简而言之,这就是问题所在:

public class InitializationProblem {
    public static class A {
        static int foo = 1 / 0;
        static String getId() {
            return "42";
        }
    }

    public static void main( String[] args ) {
        try {
            new A();
        }
        catch( Error e ) {
            // ignore the initialization error
        }

        // here an Error is being thrown again,
        // without any hint what is going wrong.
        A.getId();
    }
}

为了让它不那么容易,除了最后一次调用 A.getId() 之外,其他所有调用都隐藏在一个非常大的项目的初始化代码中。

问题:

现在我在经过数小时的反复试验后发现了这个错误,我想知道是否有一种直接的方法可以从抛出的异常开始找到这个错误。 关于如何做到这一点的任何想法?


我希望这个问题对其他分析莫名其妙的NoClassDefFoundError的人来说是一个提示。

【问题讨论】:

  • 我的第一个想法是不要抓住和忽略Errors。永远。
  • 这是从文件系统收集的一些测试类的初始化。我试图实例化每个类以查看它是否与给定接口匹配......有些类无法实例化,例如抽象的。所以我抓住了所有的错误并忽略了它们。
  • 至少,打印堆栈跟踪。一个空的 catch 块对你来说应该是一个危险信号。 (你用过 FindBugs 吗?它会比你自己更早地捕捉到很多东西。)
  • @tangens 你不需要实例化一个类来查看它是否实现了一个接口。要查看 Class<?> someUnknownClass 是否实现了某个接口 IInterface,请执行 IInterface.class.isAssignableFrom(someUnknownClass)
  • @geoff-reedy:实际上,我做了一个 Class.forName(),甚至这会导致错误。现在我正在寻找更好的方法,请参阅stackoverflow.com/questions/2210930/…

标签: java debugging noclassdeffounderror


【解决方案1】:

确实,您永远都不应该捕获错误,但这里是您可以在任何可能发生的初始化程序问题中找到它们的方法。

这是一个代理,它可以让所有 ExceptionInInitializerErrors 在创建时打印堆栈跟踪:


import java.lang.instrument.*;
import javassist.*;
import java.io.*;
import java.security.*;

public class InitializerLoggingAgent implements ClassFileTransformer {
  public static void premain(String agentArgs, Instrumentation inst) {
    inst.addTransformer(new InitializerLoggingAgent(), true);
  }

  private final ClassPool pool = new ClassPool(true);

  public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)  {
    try {
      if (className.equals("java/lang/ExceptionInInitializerError")) {
        CtClass klass = pool.makeClass(new ByteArrayInputStream(classfileBuffer));
        CtConstructor[] ctors = klass.getConstructors();
        for (int i = 0; i < ctors.length; i++) {
          ctors[i].insertAfter("this.printStackTrace();");
        }
        return klass.toBytecode();
      } else {
        return null;
      }
    } catch (Throwable t) {
      return null;
    }
  }
}

它使用 javassist 来修改类。编译并将其放入带有 javassist 类和以下 MANIFEST.MF 的 jar 文件中

Manifest-Version: 1.0
Premain-Class: InitializerLoggingAgent

使用java -javaagent:agentjar.jar MainClass 运行您的应用程序,即使捕获到每个 ExceptionInInitializerError 也会被打印出来。

【讨论】:

  • 谢谢,杰夫。这是关于如何编写代理的更好的教程,比谷歌搜索的任何内容都好。
  • 虽然这仍然只是第一次这样做,如果我没记错的话,之后会抛出NoClassDefFoundError。我们还能在后续调用中获得对原始ExceptionInInitializerError 的引用吗?
  • 谢谢,这真的很有帮助。我在尝试使用 Oracle JVM V1.7.0_80 执行此操作时遇到的一个问题是,您还需要将 Can-Retransform-Classes: true 添加到清单中,否则您会看到类似 java.lang.UnsupportedOperationException: adding retransformable transformers is not supported in this environment 的内容。显然这也适用于 IBM JVM,这就是我通过 Google 搜索找到解决方案的方式。
【解决方案2】:

我的建议是尽可能避免使用静态初始化程序来避免这个问题。因为这些初始化程序是在类加载过程中执行的,所以许多框架不能很好地处理它们,事实上旧的 VM 也不能很好地处理它们。

大多数(如果不是全部)静态初始化器可以重构为其他形式,并且通常它使问题更易于处理和诊断。正如您所发现的,静态初始化程序被禁止抛出已检查的异常,因此您必须记录并忽略,或记录并重新抛出未检查的异常,这些都不会使诊断工作变得更容易。

此外,大多数类加载器会一次性尝试加载给定的类,如果第一次失败,并且没有正确处理,问题就会被有效地解决,最终会出现一般错误被抛出,几乎没有上下文。

【讨论】:

    【解决方案3】:

    如果你曾经看到过这种模式的代码:

    } catch(...) {
    // no code
    }
    

    找出是谁写的,然后打败他们。我是认真的。试着解雇他们——他们不了解编程的调试部分,无论是形式还是形式。

    我想如果他们是学徒程序员,你可能会直接把他们打得一干二净,然后让他们有第二次机会。

    即使是临时代码,也不值得以某种方式将其引入生产代码。

    这种代码是由检查异常引起的,一个原本合理的想法变成了一个巨大的语言陷阱,因为在某些时候我们都会看到类似上面的代码。

    解决这个问题可能需要几天甚至几周的时间。所以你必须明白,通过编码,你可能会花费公司数万美元。 (还有另一个很好的解决方案,对他们因愚蠢而花掉的所有薪水罚款——我敢打赌他们再也不会这样做了)。

    如果您确实期望(捕获)给定错误并处理它,请确保:

    1. 您知道您处理的错误是该异常的唯一可能来源。
    2. 任何其他意外捕获的异常/原因都将被重新抛出或记录。
    3. 您没有捕捉到广泛的异常(Exception 或 Throwable)

    如果我听起来咄咄逼人和愤怒,那是因为我花了数周时间寻找此类隐藏的错误,而且作为一名顾问,还没有找到任何人来解决这个问题。对不起。

    【讨论】:

    • 这是一个很好的建议,但它不能回答所提出的问题。
    • 我同意。通常当一个问题得到很好的回答并且我看到我可以给出的其他建议时,看起来对这个问题感兴趣的人会发现有用的,我喜欢把它扔进去,没有害处,也许它会帮助别人。这个网站不应该只是无缘无故地遵守严格的规则,而应该是帮助他人。如果这种回答对他人造成的伤害大于帮助,我会反对,但审核系统似乎可以解决这个问题。
    • 我也有类似的问题。没有忽略错误的catch块,我第一次得到ExceptionInInitializerError,但在那之后我得到NoClassDefFoundError。有什么方法可以将其链接到原始ExceptionInInitializerError
    • @Jus12 这是一个非常糟糕的情况,因为您的对象确实无效(NoClassDefFound 只是提醒您它已损坏)。如果您有源代码,请在它发生的静态初始化程序中捕获它,修复它/记录它并且不要重新抛出。如果您不拥有源代码,请确保在首次引用它之前满足构建该对象的所有条件。这意味着您甚至无法实例化导入问题类的类!你甚至可能不得不反思地实例化它,但你最好修复它或摆脱它。
    【解决方案4】:

    错误给出的唯一提示是类的名称,并且在该类的初始化过程中出现了严重错误。因此,无论是在其中一个静态初始化程序、字段初始化中,还是在被调用的构造函数中。

    第二个错误被抛出,因为在调用 A.getId() 时类尚未初始化。第一次初始化被中止。发现这个错误对工程团队来说是一个很好的测试;-)

    定位此类错误的一个有前途的方法是在测试环境中初始化类并调试初始化(单步)代码。然后应该能够找到问题的原因。

    【讨论】:

      【解决方案5】:

      今天我花了一个下午分析 NoClassDefFoundError。在一次又一次地验证类路径后,原来有一个类的静态成员抛出了一个第一次被忽略的异常

      你的问题!永远不要捕获并忽略错误(或 Throwable)。从来没有。

      如果您继承了一些可能会执行此操作的不可靠代码,请使用您最喜欢的代码搜索工具/IDE 来查找并销毁有问题的 catch 子句。


      现在我在经过数小时的反复试验后发现了这个错误,我想知道是否有一种直接的方法可以从抛出的异常开始找到这个错误。

      不,没有。有一些复杂/英雄的方法……比如使用 Java 代理做一些聪明的事情来即时破解运行时系统……但不是典型的 Java 开发人员可能在他们的“工具箱”中拥有的那种东西。

      这就是上述建议如此重要的原因。

      【讨论】:

      • @ChristopherBarber - 现在可以了。快乐。
      【解决方案6】:

      我真的不明白你的推理。您询问“从抛出的异常开始查找此错误”,但您发现该错误并忽略它...

      【讨论】:

        【解决方案7】:

        如果您可以重现问题(即使是偶尔),并且可以在调试下运行应用程序,那么您可以在调试器中为 ExceptionInInitializerError 的(所有 3 个构造函数)设置断点,并查看它们何时git 命中。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-09-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多