Java6 と Java7 の挙動の違いは、バグではありませんでした。
前回の記事(「Java6 と Java7 の挙動の違い(バグ?)」)に、id:expf さんからコメントをいただきました。
おそらく、最初のprintStackTrace呼び出しではStackOverflowErrorが出ていると思います。
http://d.hatena.ne.jp/chiheisen/20120524/1337871478#c1339587063
その後、1つ前のTest.mainがcatch→printStackTrace呼び出し→StackOverflowError を繰り返して、少しずつ使えるスタックが増えます。
Java6のケースだと、途中で何回か「クラス名は出力できたがその後のスタック出力前にStackOverflowError」があるため、「java.lang.StackOverflowError」が何回も出力されているのだと思います。
Java7のケースでは、IdentityHashMapの初期化まで処理が進むようになると、初期化中にStackOverflowErrorが出て、1つ前のTest.mainがcatch→NoClassDefFoundError を繰り返してトップレベルまで戻っているはずです。
改めて確認してみたら、この通りの挙動でした!
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 さん、ありがとうございました!