Java6 と Java7 の挙動の違いは、バグではありませんでした。

前回の記事(「Java6 と Java7 の挙動の違い(バグ?)」)に、id:expf さんからコメントをいただきました。

おそらく、最初のprintStackTrace呼び出しではStackOverflowErrorが出ていると思います。
その後、1つ前のTest.mainがcatch→printStackTrace呼び出し→StackOverflowError を繰り返して、少しずつ使えるスタックが増えます。
Java6のケースだと、途中で何回か「クラス名は出力できたがその後のスタック出力前にStackOverflowError」があるため、「java.lang.StackOverflowError」が何回も出力されているのだと思います。
Java7のケースでは、IdentityHashMapの初期化まで処理が進むようになると、初期化中にStackOverflowErrorが出て、1つ前のTest.mainがcatch→NoClassDefFoundError を繰り返してトップレベルまで戻っているはずです。

http://d.hatena.ne.jp/chiheisen/20120524/1337871478#c1339587063

改めて確認してみたら、この通りの挙動でした!

Java6の場合

Java6 のケースだと StackOverflowError が何回も出力されて…というのを図にすると、このようになります。

StackOverflowError の中で StackOverflowError が出ているというのを、スタックに余裕ができるまで繰り返していました。
スタックトレースが出る前に、再度 StackOverflowError が出ているという挙動を、完全に誤解していました…)


念のため、以下のソースで試してみたところ、「Call main.」の出力回数(= 実際の main の呼び出し回数)と、スタックトレース中の main の呼び出し回数の記録が一致していませんでした。*1

try {
    System.out.println("Call main.");
    main(args);
} catch (Error e) {
    e.printStackTrace();
}

(具体的には、6288回 main が呼び出された後に最初の StackOverflowError が発生して、そこから printStackTrace 呼び出し→ StackOverflowError を5200回ぐらい繰り返し、やっとこさスタックトレースが出ていました。)

Java7の場合

Java7 のケースも、StackOverflowError が NoClassDefFoundError になった以外は同じような動きでした。


ただ、StackOverflowError だと、スタックに空きがでた段階でスタックトレースの出力に成功しますが、 NoClassDefFoundError だと何回やっても失敗します。*2
そして、最終的にキャッチされずに main から例外が出てしまいます。
このとき、普通はデフォルトの UncaughtExceptionHandler がスタックトレースを出してくれるのですが、今回はそこでもエラーになります。
結果として、Java仮想マシンJVM)が、きわめて簡単なメッセージだけを出して終了します。


Java6からJava7で発生するエラーが変わったのは、おそらくJava7でクラスローダに手が入った(スレッドセーフにするために書き換えられた)からだと思います。*3
Java技術最前線 - Java SE 7徹底理解 第13回 スレッドセーフクラスローダ:ITpro

結論

Java6 と Java7 の挙動の違いはありますが、バグではありませんでした。
id:expf さん、ありがとうございました!

*1:最初の StackOverflowError が出た段階でスタックトレースが出ていれば、この二つは一致するはずです。

*2:これも id:expf さんから別途コメントをいただいた通り(一度クラスの初期化に失敗すると、次に使うときにNoClassDefFoundErrorになります。)でした。

*3:ただ、よりにもよって IdentityHashMap の初期化で落ちなくても…