如何分析由忽略的ExceptionInInitializerError引起的NoClassDefFoundError?

时间:2010-02-05 21:53:22

标签: java debugging noclassdeffounderror

今天我花了我的下午分析一个NoClassDefFoundError。在一次又一次地验证类路径之后,结果发现有一个类的静态成员抛出了第一次被忽略的异常。之后,每次使用该类都会抛出一个没有有意义的堆栈跟踪的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()的最后一次调用都被隐藏在一个非常大的项目的初始化代码中。

问题:

现在我在经过数小时的试验和错误后发现了这个错误,我想知道是否有一种直接的方法从抛出的异常开始找到这个bug。 有关如何执行此操作的任何想法?


我希望这个问题可以暗示其他人分析一个莫名其妙的NoClassDefFoundError

7 个答案:

答案 0 :(得分:16)

真的,你永远不应该捕获错误,但是这里是你可以在任何地方找到初始化问题的方法。

这是一个代理,它将使所有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。

答案 1 :(得分:13)

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

大多数(如果不是全部)静态初始值设定项可以重构为其他形式,通常它会使问题更容易处理和诊断。正如您所发现的那样,静态初始值设定项被禁止抛出已检查的异常,因此您必须进行日志记录和忽略,或者记录并重新抛出未选中状态,这些都不会使诊断工作变得更容易。

此外,大多数类加载器只进行一次尝试加载给定的类,如果它第一次失败,并且处理不当,则问题会被有效压缩,最终会导致泛型错误被抛出,很少或没有背景。

答案 2 :(得分:5)

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

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

找出谁写了它并击败他们的CRAP。我是认真的。尝试让他们被解雇 - 他们无法以任何方式,形状或形式理解编程的调试部分。

我想如果他们是一名学徒程序员,你可能会打败他们,然后让他们有一次机会。

即使是临时代码 - 也不值得将它以某种方式提交到生产代码中。

这种代码是由经过检查的异常引起的,这是一个非常合理的想法,因为在某些时候我们都会看到类似上面的代码。

如果不是WEEKS可能需要DAYS来解决这个问题。所以你必须明白,通过编码,你可能会使公司花费数万美元。 (还有另一个很好的解决方案,因为那种愚蠢而花费的所有工资都很好 - 我打赌他们再也不会这样做了。)

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

  1. 您知道您处理的错误是该异常的唯一可能来源。
  2. 重新抛出或记录任何其他异常/原因。
  3. 你没有抓住广泛的例外(例外或可投掷)
  4. 如果我听起来很咄咄逼人,那就是因为我花了几周的时间才发现这样的隐患,作为一名顾问,没有找到任何人把它拿出去。遗憾。

答案 3 :(得分:1)

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

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

找到这样一个错误的有希望的方法是在测试环境中初始化类并调试初始化(单步)代码。然后人们应该能够找到问题的原因。

答案 4 :(得分:1)

  

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

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

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


  

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

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

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

答案 5 :(得分:0)

我真的不明白你的理由。你问“从抛出的异常开始发现这个bug”然后你就抓住了那个错误而忽略了它......

答案 6 :(得分:0)

如果您可以重现问题(甚至偶尔),并且可以在调试下运行应用程序,那么您可以在调试器中为(所有3个构造函数)ExceptionInInitializerError设置一个断点,看看他们什么时候开始打击。