9

今日は、午後に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()、非常に大きなプロジェクトの初期化コードのどこかに隠されていました。

質問:

何時間もの試行錯誤の末にこのエラーを見つけたので、スローされた例外から始めてこのバグを見つける簡単な方法があるかどうか疑問に思います。これを行う方法についてのアイデアはありますか?


この質問が、説明のつかないことを分析している他の人にとってのヒントになることを願っていますNoClassDefFoundError

4

7 に答える 7

17

実際には、エラーをキャッチするべきではありませんが、イニシャライザの問題が発生する可能性のある場所を見つける方法は次のとおりです。

これは、すべての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が出力されます。

于 2010-02-05T23:17:16.090 に答える
13

私のアドバイスは、静的初期化子をできるだけ避けることによってこの問題を回避することです。これらの初期化子はクラスローディングプロセス中に実行されるため、多くのフレームワークはそれらをうまく処理できず、実際、古いVMもそれらをうまく処理できません。

ほとんどの(すべてではないにしても)静的初期化子は他の形式にリファクタリングでき、一般に、問題の処理と診断が容易になります。ご存知のように、静的初期化子はチェックされた例外をスローすることを禁じられているため、log-and-ignoreまたはlog-and-rethrow-as-uncheckedのいずれかを実行する必要がありますが、いずれも診断作業を容易にするものではありません。

また、ほとんどのクラスローダーは、特定のクラスを1回だけロードしようとします。最初に失敗し、適切に処理されない場合、問題は効果的に潰され、一般的なエラーがスローされます。コンテキストがほとんどまたはまったくありません。

于 2010-02-05T22:50:44.303 に答える
5

このパターンのコードが表示された場合:

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

誰がそれを書いたかを調べて、それらからクラップを打ち負かしてください。私は真剣です。彼らを解雇してみてください-彼らはプログラミングのデバッグ部分をどのような形、形、形式でも理解していません。

彼らが見習いプログラマーであるなら、あなたは彼らのがらくたを打ち負かして、それから彼らに1秒のチャンスを与えるかもしれないと思います。

一時的なコードであっても、それが何らかの形で本番コードに持ち込まれる可能性は決してありません。

この種のコードは、チェックされた例外によって引き起こされます。それ以外の場合は、ある時点で上記のようなコードが表示されるという事実によって、合理的なアイデアが大きな言語の落とし穴になります。

この問題を解決するには、週ではなくても数日かかる場合があります。したがって、それをコーディングすることで、会社に数万ドルのコストがかかる可能性があることを理解する必要があります。(別の良い解決策があります、その愚かさのために費やされたすべての給料のためにそれらを罰金します-私は彼らが二度とそれをしないに違いありません)。

特定のエラーを予期(キャッチ)して処理する場合は、次のことを確認してください。

  1. あなたはあなたが扱ったエラーがその例外の唯一の可能な原因であることを知っています。
  2. 偶発的にキャッチされたその他の例外/原因は、再スローまたはログに記録されます。
  3. 例外(ExceptionまたはThrowable)を広範に把握していません

私が攻撃的で怒っているように聞こえるのは、このような隠れたバグを見つけるのに何週間も費やしてしまい、コンサルタントとして、それを取り除く人を見つけられなかったからです。ごめん。

于 2010-02-05T22:53:12.363 に答える
1

エラーが与える唯一のヒントは、クラスの名前と、そのクラスの初期化中に何かがひどく間違っていたことです。したがって、これらの静的初期化子の1つ、フィールド初期化、または呼び出されたコンストラクターのいずれかで。

A.getId()が呼び出されたときにクラスが初期化されていないため、2番目のエラーがスローされました。最初の初期化は中止されました。そのエラーをキャッチすることは、エンジニアリングチームにとって素晴らしいテストでした;-)

このようなエラーを見つけるための有望なアプローチは、テスト環境でクラスを初期化し、初期化(シングルステップ)コードをデバッグすることです。そうすれば、問題の原因を見つけることができるはずです。

于 2010-02-05T22:01:09.863 に答える
1

今日は、午後にNoClassDefFoundErrorの分析に費やしました。クラスパスを何度も確認した後、最初に無視された例外をスローしたクラスの静的メンバーが存在することが判明しました

あなたの問題があります!エラー(またはスロー可能)をキャッチして無視しないでください。決して。

また、これを行う可能性のある危険なコードを継承している場合は、お気に入りのコード検索ツール/ IDEを使用して、問題のcatchある句を探して破棄します。


何時間もの試行錯誤の末にこのエラーを見つけたので、スローされた例外から始めてこのバグを見つける簡単な方法があるかどうか疑問に思います。

いいえ、ありません。複雑で英雄的な方法があります...Javaエージェントで巧妙なことを行ってランタイムシステムをオンザフライでハッキングするようなものです...しかし、典型的なJava開発者が「ツールボックス」に持っているようなものではありません。

そのため、上記のアドバイスは非常に重要です。

于 2010-02-05T22:46:54.813 に答える
0

私はあなたの推論を本当に理解していません。「スローされた例外から開始してこのバグを見つける」ことについて質問しますが、そのエラーをキャッチして無視します...

于 2010-02-05T22:43:19.783 に答える
0

問題を(たまにでも)再現でき、デバッグ中のアプリを実行できる場合は、デバッガーでExceptionInInitializerError(の3つのコンストラクターすべて)のブレークポイントを設定し、それらがいつgitヒットするかを確認できる場合があります。

于 2016-05-11T09:51:15.943 に答える