Java SEバージョンアップでのトラブルの話が面白かった

Java Day Tokyo 2015で、NECJava SEバージョンアップでのトラブルの話が面白かった。
Java EEアプリケーションサーバの開発現場で見たJava SEの実際
資料はこちらで公開されてるので、資料に書かれてることはそちら参照という感じで、どんな話だったか書いてみます。
Java Day Tokyo 2015


アプリケーションサーバーを提供する中でJava SEをバージョンアップしたときに出て来たさまざまなトラブルの話と、Java SE 8から導入されたMetaspaceの話が主でした。
Java EEは機能が標準化されているので、アプリケーションサーバーはカスタマーサポートで差別化をはかるしかない、顧客から見ると、Java SEやOSまで全て含めてアプリケーションサーバーなので、全部対応していく、という話をされていました。
Javaにもそれなりにバグはあって、アプリケーションではそのバグを回避するようなコードを書くことがあるのですけど、Javaのバージョンアップのときにバグが修正されると、そのバグ回避コードが正しく動かなくなることがあるので、アプリケーションサーバー側でバグを再現するような挙動にしてアプリケーションの動きが変わらないようにすることもあるというのが面白かった。
ここではJava SEバージョンアップ時のトラブルの話を、いくつかとりあげます。番号は、資料中のものです。

1.Lambdaを使用したアプリケーションが配備できない

Java SE 8が出たばかりのときに、Lambdaを使うアプリケーションが配備できないという現象がありました。これはぼくもGlassFishを使っていて体験しました。
で、これは、バイトコード操作ライブラリのASMがクラスファイルの解析に失敗したからだったようです。Lambdaを使うと、InvokeDynamicやMethodHandleがバイトコード中にあらわれるのだけど、その解析に失敗していた、と。
InvokeDynamicやMethodHandleはJava SE 7で導入されたものですが、Java SE 8まではJava言語では使われていなかったのが、Java SE 8でLambdaがこれらを使うようになって顕在化した、と。
言語仕様が変わると思わぬところで非互換性が出てくるという話でした。

5.ロガーのインスタンスがすり替わる

Java SE 7u25で、あるタイミングからgetLoggerが別インスタンスを返すようになるという現象があるようです。そうすると、ログレベルなどの変更が無効になって困る、と。
セキュリティマネージャが有効になっているときに、sun.awt.AppContextクラスをロードするとロガー名が別管理になる、というのが理由らしい。
で、これは、タイムゾーンに関しても同様の現象があって、リリースノートにはこちらの記述はあるのだけど、ロガーのほうに影響があるという記述はなくて、暗黙の変更になってしまっているということでした。

6.getLoggerが稀にnullを返す

一番おもしろかったのがこれ。
Logger.getLogger()がnullを返すことがあるという、まごうことなきバグです。
しかも、Logger.getLogger()という使用頻度が高そうなところで出るのがすごいです。
そして、その発生理由がまた、めちゃくちゃおもしろい。


ここでロガーはキャッシュされているのだけど、メモリリークを防ぐために弱参照マップが使われているようです。そして、こんな感じのコードになっていたようです。

if(result == null){
  result = new Logger(name, null);
  addLogger(result);//弱参照マップに登録
  result = getLogger(name);//弱参照マップから取得
}
return result;

ここで、addLoggerとgetLoggerの間でGCが発生すると、Loggerインスタンスが片付けられてしまって消えてしまうことがある、と。
しかし、このコードをみる限りでは、Loggerインスタンスはresult変数が保持しているので、GC対象にならないはずです。
そのように見えます。


ところがこれ、new Loggerしたインスタンスをresultに入れても、そのままaddLoggerに渡してすぐgetLoggerで上書きされるんで、VMが、resultに入れる必要ないじゃーん、って判断してこんなコードのような挙動にしてしまうっぽいんですね。

if(result == null){
  addLogger(new Logger(name, null));//弱参照マップに登録
  result = getLogger(name);//弱参照マップから取得
}
return result;

そして、addLoggerとgetLoggerの間でGC対象になる瞬間ができてしまう、と。


いまはresultがnullの間は繰り返すというwhileループで囲むことで、このような現象に対処するコードになっています。
けど、この弱参照と最適化がからんだバグというのが、ものすごく面白いなと思いました。
この話だけでJava Day Tokyo 2015に満足した感じです。
※追記 4/29 2:05 new Loggerしたあとresultに割り当てる前にGCが走ってるんではないかという話が。こちらのほうが簡潔に説明できるので、まあこちらかな。
https://twitter.com/sipadan2003/statuses/592846940913676288
※追記 5/2 2:35 しかしそれだと、あらゆる代入でGCが起こりうるわけで、それはなさそうな気がする。


Java SEでも、こけることはあるよ、って話でした。
ということで、Java SEは後方互換性にかなり気をつかってるとはいえ、バージョンアップのときになんだかんだトラブルは出ることはあるよ、って話でした。Metaspaceの話も面白かったのですけど資料を参照ということで。