先週は,HotSpot VMのメモリー管理の手法を解説しました。

そこで,今週は実際にYoung領域やOld領域がどのように使用されているかを調べてみましょう。

HotSpot VMには非標準のオプションがいろいろあります。その中で,ヒープの情報に関するものには以下のようなものがあります。これ以外のオプションについてはJava HotSpot VM Optionsをご覧ください。

オプション 説明
-verbose:gc GCの情報
-Xloggc:filename GCの情報をファイルに出力
-XX:+PrintGCDetails GCの詳細情報
-XX:-PrintTenuringDistribution インスタンスの寿命情報
-XX:+PrintHeapAtGC GC前後のヒープの使用状況

MXBeanを使用してヒープの使用状況を見てみる

MXBeanを使用して,それぞれの領域を使用状況を参照することも可能です。

先々週と同様に,JDKのサンプルJava2Demoを例にして,jconsoleで参照してみます。Java2DemoはJDKをインストールしたディレクトリ以下のdemo/jfc/Java2Dディレクトリにあります。

> java -Dcom.sun.management.jmxremote -jar Java2Demo.jar
領域の選択
図1 領域の選択
Edenの使用状況
図2 Eden領域の使用状況
Survivor領域の使用状況
図3 Survivor領域の使用状況
Tenured領域の使用状況
図4 Tenured領域の使用状況

Java2Demoの起動が確認できたら,jconsoleを起動してみましょう。

先々週と同様に,jconsoleの「メモリ」タブで各領域の使用状況を参照できます。

左上のコンボボックスにはデフォルトで「ヒープメモリの使用状況」が選択されています。このコンボボックスを展開するとEden,Survivor,Tenuredなどが並んでいます(図1)。Perm Genなどの項目はヒープではない非ヒープ領域に属している領域です。

Survivor領域は実際には二つありますが,常にどちらかは空なので,一つにまとめて扱われているようです。

この中から,Eden領域を選択してみましょう。

Eden領域の使用量は,だいたい0から1MB程度の範囲に収まっています(図2)。

左下の詳細の部分を見ると,確定(MemoryUsageのcommitted)が1MB,最大が4MBであることがわかります。

つまり,Eden領域の使用量が1MB近くになると,Scavenge GCが発生するわけです。グラフでピークが1MBになっていないものがあるのは,グラフのサンプリング周期と実際にScavenge GCが行われた時間が合わないためです。

最大の値が4MBまでということは,Eden領域が4MBまで拡張可能であることを示しています。

また,右下にある緑の棒グラフは,ヒープおよび非ヒープの各領域の現在値を示しています。

ヒープは左からEden領域,Survivor領域,Tenured領域を示しています。

このグラフはパーセント表示になっていますが,100%がcommittedではなく,maxを表しています。

図2の状況を見ると,Java2Demoのメモリー使用量はまだ十分に余裕があることがわかります。

次にSurvivor領域を見てみましょう(図3)。

Survivor領域も左下の詳細を見てみると,committedとmaxの値がわかります。

デフォルトだと,Survivor領域は最大で448KBになることがここからわかります。図3の状況だと,サイズはまだまだ余裕ですね。

図4はTenured領域の使用状況を示しています。Java2Demoは周期的に同じ処理を繰り返すので,図4はだいたい同じ形が並ぶようになっています。

メモリー・リークがあるようなアプリケーションだと,このグラフが右肩上がりになります。残念ながら,MXBeanとjconsoleの組み合わせではメモリー・リークがあることは発見できますが,どこでリークしているかを突きとめることは困難です。

メモリー・リークを解析する場合は,別途プロファイラを使用して解析します。

左下の詳細を見てみましょう。Tenured領域のデフォルトの最大値は約60MBであることがわかります。

Client VMの場合,デフォルトでヒープのサイズは64MBであり,そのうち約60MBがOld領域,約4MBがYoung領域に割り振られています。

このOld領域とYoung領域の比がヒープ・チューニングで重要になります。

ヒープのチューニング

最後に簡単にヒープのチューニングについて触れておきましょう。

ヒープのチューニングで重要なのは,GCの頻度をなるべく抑えられるようにすることです。特にFull GCは時間がかかるので,必要最低限になるようにします。

例えば,寿命の短いインスタンスを大量に生成している場合は,Young領域が逼迫してしまいます。このため,Tenured領域まで寿命の短いインスタンスが占めることになってしまい,Full GCの頻度が高くなってしまいます。

このような場合は,Young領域のサイズを大きくすることで,Full GCの発生を抑えることができます。

ヒープのサイズを設定するためのHotSpot VMのオプションには以下のようなものがあります。ヒープ以外にも,Permanent領域のサイズ設定のためのオプションなどもあります。

オプション 説明
-Xms 起動時のヒープサイズ
-Xmx 最大のヒープサイズ
-Xmn Young領域のサイズ
-XX:NewRatio Young領域とOld領域の比率
-XX:SurvivorRatio Eden領域とSurvivor領域の比率

これらのオプションを使用して,各領域のサイズを適切に設定します。

しかし,もしお使いのJava SEのバージョンが5.0以上であれば,これらのオプションを使う必要はないかもしれません。というのは,Java SE 5.0ではGCのエルゴノミクスが導入されたからです。エルゴノミクスにより,各種パラメータの自動調整が行われます。

エルゴノミクスで調整される項目として以下のようなものがあります。

  • 初期ヒープ・サイズ,最大ヒープ・サイズ
  • GCのポリシー(アルゴリズムの選択,GCに使用するスレッド数など)
  • Young領域とOld領域のサイズ

ただし,エルゴノミクスが真価を発揮するのはマルチCPUで大量のメモリーを有しているシステムの場合です。

Java SE 6でもエルゴノミクスの機能拡張が図られており,今後はユーザーによるチューニングの必要性は減少していくかもしれません。

著者紹介 櫻庭祐一

横河電機の研究部門に勤務。同氏のJavaプログラマ向け情報ページ「Java in the Box」はあまりに有名