jstatを使用したJavaアプリのメモリ計測


Javaの使用メモリサイズの調査

Javaアプリで実際に使用しているメモリは、topコマンドでは取得不可能です。topコマンドを実行しても、JVMが全体で確保しているメモリのサイズがわかるだけです。
今回は、jstatツールを使用して、Javaの実際に使用しているメモリのサイズの大まかな値を確認する方法を説明します。本方法を使うことで、アプリの改修無しに、処理にあまり影響を与えず、大まかな使用メモリ量を見ることができます。

jstatの準備

jstatはJava仮想マシンの統計データの監視ツールです。JDKに含まれているため、JDKをインストールすれば同時にインストールされ使用可能な状態となります。ただし、公式のドキュメントによると試験的な機能とのことなので、今後のvrersionでなくなる可能性があることに注意してください。

解析用プログラム

適当な処理のサンプルアプリを動かします。Javaの起動時に以下のオプションをつけ、ヒープメモリの最大、最少サイズはどちらも20GBに指定しています。
-Xms20G -Xmx20G
開始から終了まで約3000秒かかるバッチ処理を行うアプリです。
プログラムの詳細については割愛します。

jstatの実行

サンプルアプリを起動したら、jpsコマンドでアプリのプロセスIDを確認します。

$ jps
29366 Jps
31781 sampleApp

sampleAppのプロセスIDは31781です。jstatコマンドを実行し、その結果をファイルに書き出したいと思います。以下のコマンドを実行します。

$ jstat -gcutil -t 31781 1000 > jstat.tsv

今回、1秒毎に、GCの統計情報とアプリ起動からのタイムスタンプを一緒に表示するようにしています。"jstate.tsv"という名前のファイルとして出力しています。
jstatコマンドのオプションに関しては、公式ページを参照していただきたいです。

jstatコマンドの出力

jstatコマンドの出力は以下のようになりました。

Timestamp         S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
39.8              0.00   0.00  38.00   0.00  17.13  19.75      0    0.000     0    0.000    0.000
40.8              0.00   0.00  44.00   0.00  17.13  19.75      0    0.000     0    0.000    0.000

             ~~中略~~

3019.0            63.30  74.99 100.00  53.52  96.26  86.35    753  414.206    25  581.636  995.842
3020.0            78.67  74.99 100.00  53.52  96.26  86.35    753  414.206    25  581.636  995.842

使用メモリサイズの解析

jstatコマンドの出力から、実際に使用しているメモリの調査を行っていきます。
使用しているメモリの大まかな推移を調査するには、jstatの出力の"O"(Old領域メモリ割り当て率 = 使用率)の項目だけに注目すれば大丈夫です。

"Timestamp"の数値を横軸、"O"の数値を縦軸にしたグラフが以下になります。

このグラフから、メモリの使用量を読み取ることが可能です。ただ、一見しただけではメモリの割り当て率が50% ~ 100%の間で激しく推移していて良くわかりません。
Old領域には、そのタイミングで実際に使われているデータと、もう使われていないデータが混在しているため、その中から使われているデータの量だけを見つける必要があります。
上のグラフにFull GCの実行タイミングを青の破線で重畳させたのが以下のグラフとなります。

Full GCが起こった後にメモリの使用量が低下していることがわかると思います。このFull GC直後のメモリが低下したタイミングのメモリ割り当て率のみを見ることで、アプリの実際のメモリ使用量がわかります。
下の図では、低下したタイミングの値を赤の実線で結んでいます。そして、この値がおおよその実使用メモリの推移となります。

上のグラフを確認することで、以下のようなことがわかるかと思います。

  • アプリ起動後の2900秒付近(アプリの終了間際)で、メモリの使用量が最大になる。
  • メモリの使用量最大のタイミングで使用割合は85%程度。メモリの使用量は 20GB * 0.85 = 17GB程度。

加えて、アプリのログと照らし合わせることで、各処理での大体のメモリ使用量を確認することが可能です。

本方法の簡易説明

なぜ、Old領域のFullGC後のサイズを見るだけで、メモリの使用量がわかるのでしょうか?
理解するにはJVMのメモリ管理の仕組みを知る必要がありますが、以下非常に単純化したものを順に説明します。

  1. Javaのアプリケーションでは、ある程度の期間メモリに残っているデータは、全てメモリのOld領域に保存される。
  2. Old領域がいっぱい(90%以上程度)になったら、Full GCが走り、Old領域のデータを全て精査、使用されていないデータがあったらメモリを解放し、使われているデータのみをOld領域に残す。
  3. FullGC直後のOld領域のデータ量を見ることで、解放のできない(アプリで実際に使用中の)メモリ量を知ることができる。

まとめ

jstatコマンドを使用したJavaアプリのメモリ使用量の確認方法をまとめました。
この方法でざっくりと処理毎のメモリの使用量を確認し、その後、特にメモリを使用している処理において、各オブジェクトのメモリ使用量を調査していく流れが良いかと思います。