JavaのWeb開発の開発後期になると性能試験や負荷試験を実施することになると思いますが、
そのフェーズになると色々な問題が起こることが多い。
今まで起きた問題と調査・解決方法をいくつかの回に分けて紹介しようと思います。
まずはメモリリーク。
長時間サーバを起動して運用していたり、負荷試験を実施するとメモリリークを起こすことがある。
ガベージコレクションのおさらい
Javaのヒープは大きくnew領域(young領域)とold領域に分かれます。
new領域には生成されてすぐのオブジェクトが格納されてマイナーGCにて未使用になった際に開放されます。
マイナーGCを何度も繰り返されても開放されない(長く参照されている)オブジェクトは
old領域へと移動され、こちらはメジャーGC(フルGC)で開放されます。(メジャーGCはnewとperm領域も開放。)
もう少し細かく説明すると・・・
new領域はeden、from、toに分かれます。
生まれたてのオブジェクトはedenに格納。
メジャーGC発生の際にまだ参照されている(生きている)オブジェクトは
eden領域からfrom領域へ移されます。
次のGCの際にfromにあったオブジェクトがまだ生きていたらto領域へ移されます。
厳密にはedenからtoへ移動、次のGCの際にfromとtoの名前が逆転するので
fromからtoへ移動・・・さらに次でまた逆転してfromからtoへ・・・という感じ。
from⇔toの移動が規定回数(デフォルト32回)繰り返したらold領域へ移動させられます。
マイナーGCに比べるとメジャーGCはコストがかかります。
GCは全スレッド停止(Stop the world)ことがあり、その間はプログラムも停止します。
なので頻繁なGCは避けたいし、GCで適切にヒープが開放されるかを気にする必要があります。
※パラレルGC、コンカレントGCなどなるべくコストをかけない仕組みもありますが、
この辺りはググって下さい。
jstatコマンドでヒープの利用状況やGCの頻度を確認
GCやヒープの状況を調べる方法は色々あるけど
まずはjstatで手軽に調べる方法を取ります。
- 対象のプロセスの特定
jps -v | less -SN
jpsコマンドはjavaのプロセスを確認するコマンドです。jdkのbinをpathに通しておけば実行可能。
-vオプションをつけると起動オプションを表示されるのでサーバに複数のインスタンス(JVM)を
立ち上げている場合などはこれで対象を特定してプロセスIDを特定しておきましょう。
- jstatの実行
jstat -gcutil -h3 708 1000
jstat -gcutilでヒープやGCの状態をリアルタイムで確認できます。
-h3は3行ごとにヘッダを表示、
708はプロセスIDなので環境に応じたプロセスIDを指定してください。
最後の1000は1秒ごとに監視する(行を増やす)という意味です。
-gcoldに変更するとold領域に関してもう少し詳細な情報が表示されます。
ヘッダがEの列がnew領域のメモリ使用率。
new領域なのでどんどん増えてはマイナーGCが発生して減って・・・を繰り返します。
YGCがマイナーGCの実施回数なのでこれのカウントアップと同時にnew領域の使用率が減るのが正常になります。
Oの列がold領域、FGCがメジャーCGの回数になります。
new領域が全然下がらず100に張り付きっぱなしになって、OutOfMemoryErrorが起きる場合は
どこかでいっきにオブジェクトを生成しているので処理を見直すか、
ヒープ(new領域)のサイズを増やす必要があるかなと。
また、マイナーGCを繰り返してold領域が増えていきメジャーGCが頻発する・・・
場合はメモリリークの疑いやnew領域のサイズまたはプログラムが適切ではない可能性があります。
old領域がなかなか開放されない場合もいずれOutOfMemoryErrorを起こす可能性があります。
今回はここまで。
次回はGCログから見るヒープの開放率やGC時間についてを予定しています。
その後はメモリダンプやスレッド滞留時の調査方法などなど。