Javaはどのように動くのか~図解でわかるJVMの仕組み

第8回イレギュラーなヒープの動作を理解する

Tenured領域を早く使ってしまうパターン

前回ご紹介したように、HotSpotのヒープでは、アプリケーションがオブジェクトを作成するとまずはじめにEden領域が割り当てられ、マイナーGCによってSuvivor領域、Tenured領域へと移動していく流れが一般的でした。

しかし、このパターンではないイレギュラーなパターンがいくつか存在します。

その1つが、⁠オブジェクトが一般的なパターンに比べ、早くTenured領域に移動してしまう」というものです。

図1 Tenured領域を早く使ってしまう例
図1 Tenured領域を早く使ってしまう例

Tenured領域はメジャーGCの対象であり、メジャーGCはNew領域を対象とするマイナーGCに比べ、はるかに停止時間が長くなります。そのため、このようなパターンが頻繁に起こる場合は、メジャーGCの多発によってアプリケーションの停止時間が増加します。

図2 Tenured領域を早く使ってしまう例(その2)
図2 Tenured領域を早く使ってしまう例(その2)

このようなイレギュラーなパターンを知ることで、より正確にアプリケーションの特性を分析できるようになり、JVMの各領域のサイズが適切に設定できているのかを把握できるようになります。

オブジェクトがEden領域に入らないときの2つの解決法

アプリケーションが新たなオブジェクトを作成すると、HotSpotはEden領域を割り当てることは以前紹介しました。

Eden領域を割り当てるとき、Eden領域に空き領域がない場合は、マイナーGCによってEden領域を再利用できるようにしてから、Eden領域を割り当てます。

しかし、アプリケーションが作成したいオブジェクトのサイズがEden領域より大きいと、マイナーGCによってEden領域の空き容量が増えたとしても、Eden領域を割り当てることはできません。

図3 Eden領域に生成できないオブジェクト
図3 Eden領域に生成できないオブジェクト

Eden領域に入りきらないからといって、すぐにはOut Of Memory Error(OOME)を発生させてアプリケーションを強制終了させるようなことはしません。

これは、ほんの一時的な問題かもしれません。一時的な問題のために、OOMEによってアプリケーションを強制終了するよりも、アプリケーションの停止時間を長くしてでも、アプリケーションの継続実行を目指すのが現実的な解決策です。

その方法は、Tenured領域の空き容量がオブジェクトのサイズよりも大きいか小さいかによって、以下のように異なります。

Tenured領域の空き容量がこのオブジェクトのサイズよりも大きい場合

この場合、オブジェクトは、New領域を飛ばして、いきなりTenured領域を割り当てられます。一般的なパターンでは、オブジェクトはマイナーGCによってTenured領域へ移動されますが、この場合はマイナーGCが行われません。

図4 若い領域を介さずにオブジェクトを割り当てる
図4 若い領域を介さずにオブジェクトを割り当てる

Tenured領域の空き容量がこのオブジェクトのサイズよりも小さい場合

この場合、まずメジャーGCが実行され、Tenured領域内の死んだオブジェクトが解放されます。

図5 Tenured領域に死んだオブジェクトがある例
図5 Tenured領域に死んだオブジェクトがある例

オブジェクトが解放された結果、Tenured領域の空き容量がこのオブジェクトサイズを超えた場合には、Tenured領域が割り当てられます。

図6 Tenured領域をGCしてからオブジェクトを生成する
図6 Tenured領域をGCしてからオブジェクトを生成する

ただし、空き容量があっても断片化によっては割り当てられないこともあるため注意が必要です。そのような場合、どの領域にも割り当てられないことになるため、Out Of Memory Errorとなり、アプリケーションは強制停止してしまいます。

Survivor領域に入らないオブジェクトはどのように処理されるのか

Survivor領域のサイズは、必ずEden領域のサイズ以下になります。

アプリケーションがSurvivor領域のサイズを超える巨大なオブジェクトを作成し、Eden領域を割り当てられてからマイナーGCが行われると、このオブジェクトはSurvivor領域に入りません。そのような、Survivor領域よりも大きなオブジェクトは、Survivor領域を経由せず、いきなりTenured領域へ移動されます。

図7 Suvivorを介さない例
図7 Suvivorを介さない例

また、Eden領域に生きているオブジェクトが多いと、すべてのオブジェクトはマイナーGCでSurvivor領域に入りきません。

このように、マイナーGCでEden領域からSuvivor領域へコピーされる際にコピー先のSurvivor領域からあふれてしまう場合も、オブジェクトはTenured領域へ移動します。

図8 Suvivorがあふれる例
図8 Suvivorがあふれる例

このようなパターンが頻繁に発生してしまう場合、本来はEden領域やSurvivor領域で消すべきオブジェクトがTenured領域へ移動してしまい、メジャーGCを引き起こす原因となってしまいます。

今回の例では、Tenured領域には余裕があるにも関わらず割り当てられるオブジェクトの寿命とサイズに比べNew領域が小さいです。長時間生きるオブジェクトが少なく、短命~中期間生きるオブジェクトが多く生成されるのであれば、New領域を増やすことを検討しましょう。

また、ヒープの各領域の割合だけではどうしようもなく、メモリに余裕がある場合は、ヒープサイズの拡大も検討してください。ヒープサイズを拡大する場合は、第3回で紹介した停止時間を検討項目として挙げ、アプリケーションの停止時間の要件を考慮しましょう。

おすすめ記事

記事・ニュース一覧