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

Java 如何分析由忽略的ExceptionInInitializerError引起的NoClassDefFoundError?,java,debugging,noclassdeffounderror,Java,Debugging,Noclassdeffounderror,今天下午我分析了一个错误。在一次又一次地验证类路径之后,发现有一个类的静态成员抛出了一个第一次被忽略的异常。在此之后,每次使用该类都会抛出一个NoClassDefFoundError,但没有有意义的stacktrace: Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class InitializationProblem$A at InitializationPro

今天下午我分析了一个错误。在一次又一次地验证类路径之后,发现有一个类的静态成员抛出了一个第一次被忽略的异常。在此之后,每次使用该类都会抛出一个NoClassDefFoundError,但没有有意义的stacktrace:

Exception in thread "main" java.lang.NoClassDefFoundError: 
    Could not initialize class InitializationProblem$A
    at InitializationProblem.main(InitializationProblem.java:19)
就这些。不要再排队了

简而言之,这就是问题所在: 为了不那么容易,除了最后一次调用A.getId()之外,所有调用都隐藏在一个非常大的项目的初始化代码中的某个地方

问题: 经过几个小时的反复试验,我发现了这个错误,我想知道是否有一种直接的方法可以从抛出的异常开始查找这个错误有什么办法吗?



我希望这个问题能给其他分析莫名其妙的
NoClassDefFoundError

错误的人一个提示。错误给出的唯一提示是类的名称,以及在初始化该类时出现了严重错误。所以,要么在这些静态初始化器中,要么在字段初始化中,要么在被调用的构造函数中

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


查找此类错误的一种有希望的方法是在测试环境中初始化类并调试初始化(单步)代码。然后你就可以找到问题的原因了。

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

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

这就是你的问题!永远不要捕捉并忽略错误(或丢弃)。从来没有

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


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

不,没有。有复杂的/英雄的方式。。。就像用java代理做一些聪明的事情来攻击运行时系统一样。。。但这不是典型的Java开发人员在“工具箱”中可能拥有的东西


这就是为什么上面的建议如此重要。

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

大多数(如果不是全部的话)静态初始值设定项都可以重构成其他形式,一般来说,这使得问题更容易处理和诊断。正如您所发现的,静态初始值设定项被禁止抛出已检查的异常,因此您必须要么记录并忽略,要么记录并重新显示为未检查,所有这些都不会使诊断工作变得更容易


此外,大多数类加载器只尝试加载一个给定的类,如果第一次失败,并且处理不当,问题就会被有效地解决,最终抛出的是泛型错误,几乎没有或没有上下文。

如果您看到具有此模式的代码:

} catch(...) {
// no code
}
找出是谁写的,把他们打得屁滚尿流。我是认真的。试着让他们被炒鱿鱼——他们不理解编程的调试部分,不管是以何种方式,以何种形式

我想如果他们是一个学徒程序员,你可以把他们打得一败涂地,然后让他们有第二次机会

即使是临时代码——它也不值得以某种方式被引入到生产代码中

这种代码是由检查过的异常引起的,这是一个合理的想法,但由于在某个时候我们都会看到上面这样的代码,它变成了一个巨大的语言陷阱

解决这个问题可能需要几天甚至几周的时间。所以你必须明白,通过编码,你可能会让公司损失数万美元。(还有一个很好的解决办法,就是对他们处以罚款,因为他们太愚蠢了——我打赌他们再也不会这样做了)

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

  • 您知道您处理的错误是该异常的唯一可能来源
  • 意外发现的任何其他异常/原因都会被重新记录或记录
  • 您没有捕获到广泛的异常(异常或可丢弃)

  • 如果我听起来咄咄逼人和愤怒,那是因为我花了数周的时间去寻找像这样隐藏的bug,而作为一名顾问,我还没有找到任何人来发泄。很抱歉。

    真的,您不应该捕捉到错误,但下面是如何发现可能出现的初始化器问题

    以下是一个代理,它将使所有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;
        }
      }
    }
    

    使用
    java-javaagent:agentjar.jar MainClass运行应用程序,即使捕获到异常初始化错误,也会打印每个异常初始化错误。

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

    我的第一个想法是不要捕捉并忽略
    错误。这是从f收集的一些测试类的初始化
    
    
    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;
        }
      }
    }
    
    Manifest-Version: 1.0
    Premain-Class: InitializerLoggingAgent