情報科学屋さんを目指す人のメモ

方法・手順・解説を書き残すブログ。私と同じことを繰り返さずに済むように。

Eclipseのステップ実行時になぜか「ClassNotFoundException」が表示された理由

Eclipse (60) Java (38) デバッグ (3)

Eclipseで、通常実行時には何も問題なさそうなのに、「new」を含む行で「Step Into」したときにだけ、なぜか「ClassNotFoundException」が表示されてしまいました。同じ状況になって疑問に思った人向けに、詳細を書いておきます。

Step IntoしたときだけClassNotFoundExceptionが出る

通常実行しても例外で実行が止まったりしないのに、Step Intoしたときだけ突如「ClassNotFoundException」が表示されてしまいました

特徴1:表示されるCall Stack

そのときDebug Viewにはこんな呼び出し履歴が表示されます。

ClassNotFoundException(Throwable).<init>(String, Throwable) line: 286	
ClassNotFoundException(Exception).<init>(String, Throwable) line: not available	
ClassNotFoundException(ReflectiveOperationException).<init>(String, Throwable) line: not available	
ClassNotFoundException.<init>(String) line: not available	
URLClassLoader$1.run() line: not available	
URLClassLoader$1.run() line: not available	
AccessController.doPrivileged(PrivilegedExceptionAction<T>, AccessControlContext) line: not available [native method]	
Launcher$ExtClassLoader(URLClassLoader).findClass(String) line: not available	
Launcher$ExtClassLoader(ClassLoader).loadClass(String, boolean) line: not available	
Launcher$AppClassLoader(ClassLoader).loadClass(String, boolean) line: not available	
Launcher$AppClassLoader.loadClass(String, boolean) line: not available	
Launcher$AppClassLoader(ClassLoader).loadClass(String) line: not available

下の行に書いてあるメソッドから順番に呼ばれ、一番上の「ClassNotFoundException」で(一時)停止していることが分かります。

特徴2:ちゃんと実行できるから本当にクラスが見つからないわけではない

通常時には問題なく、「Step Into」したときだけ表示されるため、本当に"クラスが見つかっていない"わけではありません。

特徴3:表示されるのは、クラスを最初に「new」するときだけ

また、「ClassNotFoundException」になるのが、初回のインスタンス化時だけという特徴があります。

原因:ClassLoaderが必ず一度はClassNotFoundExceptionでloadClassに失敗する

クラスローダーの「loadClass」メソッドでは、まず親のクラスローダーで「loadClass」を実行します。このとき、見つからなかったことを「ClassNotFoundException」を使ってを呼び出し元の子クラスローダーに通知しているなのです。なので、最終的に見つかっても、「ClassNotFoundException」のコードを通過する、というわけです。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

(java.lang.ClassLoader#loadClass)

つまり、今回の「ClassNotFoundException」は、本当にクラスが見つからなかったのではなく、このクラスローダの担当範囲の中には見つからなかった、という意味だったのです

疑問1:どうして「Step Into」のときだけ?

では、「通常実行のときはどうして何も問題ないのか」、と思う人もいると思うので、説明しておきます。

じつはよく「Debug View」の表示を見てみると、通常Exceptionが発生したときにDebugが停止したのなら、「Thread」の横の表示は

Thread [main] (Suspended (exception ArrayIndexOutOfBoundsException))

なのですが、今回は次のように表示されています。

Thread [main] (Suspend)

つまり、あくまでStep Intoで呼び出し先に潜っただけで、例外が発生して停止したわけではないのです。ただ一時停止しているだけだったのです。

疑問2:じゃぁどうして突然Call Stackの表示が一気に伸びたのか

普通、Step Intoを実行した場合、Call Stackの表示は呼び出し1回分で1つしか増えないはずです。しかし、今回はまるで例外にひっかかったときのようにCall Stackがいきなり増えています

これにも理由があって、「line:」の部分に注目すると、途中の行全てが「line: not available」か「line: not available [native method]」となっています。つまり、Step Intoするにも、実行する行が特定できないため、飛び越えてしまったとわけです(この理由と解決方法についてはこちら)。

対策:ClassLoaderにStepIntoしないようにする設定方法

理由が分かってすっきりするわけなのですが、実際問題、ClassLoaderの中にStep Intoする必要はほとんど無く、そもそもさっさとコンストラクタに行くなりしてほしいところです。

そういう場合は、java.lang.ClassLoaderを無視する(飛ばす)設定ができる「Step Filter」を利用するのがおすすめです。

Step Filterの設定方法については「Step Filterの設定方法」をご覧ください。

コメント(0)

新しいコメントを投稿