1回目のマイナーGCまでの流れを把握する
前回 は、HotSpot JVM(以下HotSpot)のヒープ構造を解説しました。今回は、HotSpot JVMの4つのヒープ領域がどのように使用されていくのかを見ていきましょう。
まず、アプリケーションがオブジェクトを作成すると、HotSpotはそのオブジェクトにEden領域を割り当てます。
図1 オブジェクトが生成されるとEden領域が割り当てられる
アプリケーションが処理を実行していくと、オブジェクトがどんどん作成されていきます。その結果、Eden領域が次々と使用されていきます。
Eden領域がいっぱいになり、空き領域がなくなると、HotSpotはNew領域を対象にGCを行います。このGCは「マイナーGC」と呼ばれ、世代別GCで言う「若い世代のGC」になります。
このように、Eden領域で短命なオブジェクトを回収することで、ヒープを再利用できるようにするわけです。
図2 マイナーGC前のヒープ
1回目のマイナーGCでは、以下の構成でコピーGCが行われます。
From領域 ⇒Eden領域、Survivor 0領域
To領域 ⇒Survivor 1領域
図3 1回目のマイナーGCでの各領域の役割
しかし、1回目のマイナーGCではSurvivor 0領域にはオブジェクトが割り当てられてないため、生きているオブジェクトはEden領域からのみコピーされます。
図4 1回目のマイナーGCでのコピーGC
マイナーGCのコピー処理が終わると、Eden領域にある「死んだオブジェクト」と「コピーされたオブジェクト」が削除されます。これにより、解放されたEden領域が再利用できるようになります。
そして、Survivor 1領域が新たなFrom領域となり、Survivor 0領域は新たなTo領域となります。
図5 1回目のマイナーGC後のヒープ
2回目のマイナーGCでは動作が異なる
1回目のGCが終わり、再びアプリケーションが処理を実行していくと、オブジェクトが作成されていきます。再びEden領域がいっぱいになって空き領域がなくなると、2回目のマイナーGCが実行されます。
2回目のマイナーGCでは、以下の構成でコピーGCが行われます。
From領域 ⇒Eden領域、Survivor 1領域
To領域 ⇒Survivor 0領域
図6 2回目のマイナーGCでの各領域の役割
Survivor 1領域には、前回のマイナーGCで生き残ったオブジェクトがあるため、1回目のGCとわずかに動作が異なります。
Survivor 1領域にあるオブジェクトの中には、引き続き生きているオブジェクトと、1回目のGC以降に死んでしまったオブジェクトがあります。2回目のGCでは、以下のオブジェクトが、To領域であるSurvivor 0領域へコピーされます。
Eden領域にある生きているオブジェクト
Survivor 1領域で引き続き生きているオブジェクト
図7 2回目のマイナーGC
オブジェクトのコピーが終わると、New領域を再利用するために、以下のオブジェクトが削除されます。
Eden領域にある「死んだオブジェクト」と「コピーされたオブジェクト」
Survivor 1領域にある「すべてのオブジェクト」
そして、Survivor 0領域をFrom領域、Survivor 1領域をTo領域に変更して、GCを終了します。
図8 2回目のマイナーGC後のヒープ
3回目以降のマイナーGCでは、Survivor 0と1領域の役割がFrom領域とTo領域で入れ替わって実行されます。
マイナーGCでEden領域がTo領域にならない点に注意
HotSpotのGCで注意していただきたいのは、一般的なコピーGCではFrom領域とTo領域が入れ替わるものの、HotSpotのマイナーGCではEden領域がTo領域になることがない点です。Eden領域は、常にFrom領域としてGCされます。
図9 Eden領域へはコピーされない
長時間生きているオブジェクトは、マイナーGCが行われるたびに、Survivor0と1を移動します。このようなオブジェクトは、デフォルトではマイナーGCを16回経験すると、Tenured領域にコピーされます。
図10 Tenured領域に移動するオブジェクト
Tenured領域へ移動すると、長期間生きているオブジェクトは、マイナーGCの対象とされなくなります。
図11 Tenured領域への移動を含むGC後の状態
ここまでご紹介したオブジェクトの移動をまとめると、以下の流れになります。
図12 GCによるオブジェクトの遷移
停止時間を短くするには
オブジェクトをコピーするには、CPUリソースをたくさん消費しなければなりません。オブジェクトをEden領域からTenured領域まで移動するには、コピーGCが多く実行され、停止時間が長くなってしまいます。
停止時間を短くするためには、寿命の短いオブジェクトをSurvivor領域やTenured領域で削除するのではなく、Eden領域で削除するようなヒープ設計を心がけなければなりません。
図13 マイナーGCによるオブジェクトのコピー
たとえば、アプリケーションで一時的に使用されるすべてのオブジェクトがマイナーGC 2回目で削除される場合を考えてみましょう。
図14 すべてのオブジェクトがマイナーGC 2回目で消される例
この場合、ヒープサイズやEden領域のサイズを大きくすれば、マイナーGC 1回で消せるようになるかもしれません。
図15 Eden領域を拡張するとマイナーGC 1回目で消される例
各領域のサイズは、アプリケーションによって生成されるオブジェクト量と、その寿命のバランスから検討します。
イレギュラーなパターンも知っておく必要が
以上が、HotSpotのヒープ構造内をオブジェクトが移動していく流れはいかがでしたでしょうか。
HotSpot JVMのヒープ内でオブジェクトが移動していく一般的なパターンです。このほかに、「 4つの領域のいずれかが足りなくなってしまう」など、JVMがOut Of Memory Errorを引き起こして、アプリケーションを強制停止せざるをえなくなるかもしれません。アプリケーションが停止してしまうと、システムの停止にもつながってしまい、システムによっては社会的な問題も引き起こされるかもしれません。
次回は、そのような問題を回避するために知っておく必要がある、イレギュラーなパターンをご紹介します。