@ITのニュースで見たのですが、日立が「Full GCの起きないアプリケーションサーバ」を発売するそうです。それはすごい。
@IT News Insightより、
「Cosminexus Version 8」発表
日立がアプリサーバ新版、Full GC回避し「世界を止めない」
私は、Java関係の研修で、たまにJavaのJVMオプションによるチューニングとかGCの動作なんかについて扱うこともあるのですが、Full GCの発生によるStop The Worldを回避するには、ヒープメモリの最大値(-Xmx)を増やすとか、オブジェクトリークを最小限にするよう(いらなくなったらすぐGCにかかるように)コーディングを工夫するとか、複数CPUマシンならコンカレントコレクタ(-XX:+UseConcMarkSweepGC)を使ってGCをアプリケーションスレッドと並列で走らせるとか、いくつかの方策が考えられると思うのですが、このアプリサーバはJVMの基本的な仕組みに手を入れる形で解決を試みているのですね。特定のオブジェクトを、ヒープとは別のメモリに格納するというのは、かなり大胆な発想といえそうです。
記事ではセッションオブジェクトに注目しているようですが、確かに、同時ユーザー数が多ければ、セッションオブジェクトの量もそれにつれて多くなりますので、比較的小さい容量に設定することが多いYoung世代(記事ではNew領域)はすぐに溢れ、Tenured世代(記事ではOld領域)に多くのオブジェクトが溜まっていくことが考えられます。Tenured世代はコンカレントコレクタなどの特殊な方式を使わない限りは、Tenured全体が溢れてFull GCが起こるまでは消費量が減ることはありません。オブジェクトが溢れるまでじっと待ちつづけるのではなく、ユーザーがログアウトして不要になったセッションオブジェクトを、その時々で消去していくことができれば、確かに、Full GCは起こりにくくなると言えそうです。非常にアグレッシブな方法ですね。
あとは、私のヘボな思いつきですと、Young世代の大きさを(-Xmnとかで)かなり大きくしておいて、マイナーコレクションをたくさん発生させて凌ぐ(マイナーコレクションの実施コストはFull GCと比べて格段に少ないので、多少回数が多くても、Full GCに比べればコストは少ないのでは?と思う)とか、セッションオブジェクト自体をプーリングして一定数以上のオブジェクトが作られないようにするとか(とはいえセッションオブジェクト自体を再利用したところで、セッションオブジェクト内に格納しているデータを初期化していたら、結局、それまで使っていた情報が不要オブジェクトとして溜まっていきそうなのでダメっぽいですかね)、ありそうですけど・・・。
日立のこのアプリサーバは、そういうチューニング的なことをしなくても、ただただ普通にコードを組むだけで、Full GCを回避してくれる、面倒見のいいアプリサーバを目指しているのでしょうね。
—-
Young世代の大きさを(-Xmnとかで)かなり大きくしておいて、マイナーコレクションをたくさん発生させて凌ぐ(マイナーコレクションの実施コストはFull GCと比べて格段に少ないので、多少回数が多くても、Full GCに比べればコストは少ないのでは?と思う)
—–
GCのコストは、GCするメモリサイズに比例するので、Young世代を大きくすれば、結局FullGCのコストに近くなります。
(特にSurvivor領域でピンポンするコストは大)
——–
セッションオブジェクト自体をプーリングして一定数以上のオブジェクトが作られないようにするとか(とはいえセッションオブジェクト自体を再利用したところで、セッションオブジェクト内に格納しているデータを初期化していたら、結局、それまで使っていた情報が不要オブジェクトとして溜まっていきそうなのでダメっぽいですかね)
——–
「セッションオブジェクト」というのは、各クライアントからのログイン個々の情報を入れるモノなので、おっしゃるとおり「セッションオブジェクトの根っこのみプーリング」しても、その下のオブジェクトはTenured世代に蓄積してしまうので、うまくいかないですね
GCオタクさん>
コメントありがとうございます。
>GCのコストは、GCするメモリサイズに比例するので、Young世代を大きくすれば、結局FullGCのコストに近くなります。
GCのコストがメモリサイズに比例するであろうことは、理解できるのですが、同じ容量に対してであれば、例えばYoungが10MBのヒープに対するマイナーコレクションの方が、Young+Tenuredが10MBのヒープにFullGCするよりは1回あたりのコストは小さいですよね。もしこれらが同じコストなのであれば、世代別にこそすれ、コレクションの方式を分ける必要もないと思いますので。
Youngを大きくすればマイナーコレクションのコストも確かに大きくなりますから、FullGCのコストに「近づく」とは思うのですが、依然としてその差はかなりあるように思いますが・・・。
何か認識違いがあるようでしたら、ご教授ください。
>「セッションオブジェクトの根っこのみプーリング」しても、その下のオブジェクトはTenured世代に蓄積してしまうので、うまくいかないですね
こちらについては私の認識が誤っていたわけではなさそうなので、安心しました。
社長さん、返信ありがとうございます。
———–
同じ容量に対してであれば、例えばYoungが10MBのヒープに対するマイナーコレクションの方が、Young+Tenuredが10MBのヒープにFullGCするよりは1回あたりのコストは小さいですよね。もしこれらが同じコストなのであれば、世代別にこそすれ、コレクションの方式を分ける必要もないと思いますので。
———–
何故マイナーコレクションが通常、実行コスト小で済んでいるのか、ということだと思います。
GCというのは、
ある領域(マイナーコレクションならばYoung領域)への参照有無をすべて検出する処理。
ということであり、対象がYoungということは、新たに作られたオブジェクトである、すなわち、前回のマイナーコレクションから、新たにオブジェクトへのポインタを更新したもののみ対象として処理すれば、上記検出が可能であると言うことになります。
従って、オブジェクトへのポインタの更新を行う命令実行(JavaVMが行います)時に、同時に、個々に更新を行ったという記録を残すことで(「Writeバリア」といいます)、マイナーコレクション時にこの検出を行えるようになってます。だからマイナーコレクションはコスト小で行えているのです。
これを踏まえて、Young領域を増やした場合、どうなるかというと、Young領域が、なかなかいっぱいになりませんのでマイナーコレクションの実行頻度が下がります。そうすると、上記Writeバリアで「更新した」と検出される部分が多くなります。よって、Writeバリアによる高速化手法を用いないFullGCと、GCコストが近づいていく、、
ということになります.
日立Cosminexus V8の機能は「GC対象外領域」に追い出すことで、こういった煩わしいことを考えずに済ませているというみたいです。
http://www.hitachi.co.jp/Prod/comp/soft1/events/report/omw_200811cosmi/pdf/cc-3.pdf
GCオタクさん>
Writeバリアがマイナーコレクションの効率化にも活かされているとは知りませんでした。確かに検出範囲を限定するには有効そうですね。
GCは奥が深いですね。私ももっと勉強しなければいけませんが、またこの話題でGCオタクさんにご教授をお願いするかもしれませんので、その際は是非お付き合いください。