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

tan*_*ens 9 java debugging noclassdeffounderror

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

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

就这样.没有更多的线条.

减少到这一点,这是问题:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

题:

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


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

Geo*_*edy 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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Manifest-Version: 1.0
Premain-Class: InitializerLoggingAgent
Run Code Online (Sandbox Code Playgroud)

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


ska*_*man 13

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

大多数(如果不是全部)静态初始化器可以重构为其他形式,并且通常它使问题更容易处理和诊断.正如您所发现的那样,静态初始化器被禁止抛出已检查的异常,因此您必须进行log-and-ignore,或者log-and-rethrow-as-unchecked,这些都不会使诊断工作变得更容易.

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


Bil*_*l K 5

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

} catch(...) {
// no code
}
Run Code Online (Sandbox Code Playgroud)

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

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

即使对于临时代码 - 它也永远不值得将它以某种方式提交到生产代码中.

这种代码是通过检查异常,由一个事实,即在某些时候,我们都会看到像上面这样的代码制作成一个巨大的陷阱语言原本合理的想法造成的.

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

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

  1. 您知道您处理的错误是该异常的唯一可能来源.
  2. 偶然捕获的任何其他异常/原因要么被重新抛出,要么被记录.
  3. 你没有抓住广泛的例外(Exception或Throwable)

如果我听起来很咄咄逼人,那就是因为我花了几周的时间才发现这样的隐患,作为一名顾问,没有找到任何人把它拿出去.抱歉.