はじめに
Javaの性能に関わる重要なポイント、特にメモリ管理について解説します。Javaのメモリ管理は複雑で、性能問題が起きると対処に時間がかかることがあります。本記事では、JVMの仕組み、メモリ空間の使い方、ガベージコレクション(GC)についてまとめています。ぜひ参考にしてください。
JVMとは
JVMとはJava Virtual Machineの略で、Javaプログラムを動かすために必要なソフトウェアです。Javaの特徴のひとつに、作ったプログラムをOSに依存せずに動作させられるという点があります。そのため、JavaではOSに依存しない「Javaバイトコード」という形式でプログラムを配布します。しかし、JavaバイトコードはOS上で直接実行できません。そこで、各OSにインストールされたJVMがJavaバイトコードを実行環境に適したネイティブコードに変換することで、異なるOSでもJavaプログラムを実行できる仕組みになっています。

Java(JVM)のメモリ空間の使い方
JVMが使用するメモリ空間の内訳は下記の通りとなります。後続のガベージコレクションの仕組みやチューニング方法を理解するために抑えておく必要がある情報になりますので、ぜひご参照ください。

| 項目 | 説明 |
|---|---|
| Java Heap(ヒープ領域) | ・Javaオブジェクトを格納する領域で、GC(ガベージコレクション)が管理 ・Young領域(New)とOld領域(Tenured)に分かれる ・使用するGCアルゴリズムによって管理方法が異なる |
| Young(New)領域 | ・寿命の短いオブジェクトを格納 ・Eden領域とSurvivor領域に分かれる ・Edenがいっぱいになると Minor GC(G1 GCではYoung GC)が発生し、生存オブジェクトはSurvivor領域に移動 |
| Eden | ・新しいJavaオブジェクトが配置された際に最初に配置される領域 |
| Survivor(From / To) | ・Minor GCによってEden領域から移動されるメモリ領域 ・GCのたびに役割が入れ替わる領域 ・生存したオブジェクトを一時的に保持 |
| Old(Tenured) | ・長寿命オブジェクトを格納 ・Young領域で一定回数以上生存したオブジェクトが移動 ・G1 GCでは「Old Generation」と呼ばれ、Region単位で管理 |
| Native Memory | ・JVMが内部処理のために使用しているメモリ |
| Metaspace | ・ロードされたclassやメソッドなどを格納 ・Native Memory上に確保され、動的に拡張される ・Java8以前ではPermanent領域と呼ばれていた領域です。 |
| C Heap | ・JVM自身が使用するメモリ領域 |
| Thread Stack | ・各スレッドにOSが割り当てるメモリ領域 ・メソッド呼び出しやローカル変数を管理 |
特にJava Heapについて、「なぜメモリ領域を細かく分ける必要があるのか」と疑問に思う方もいるかもしれません。大きな理由は、ガーベジコレクション(GC)の効率を高めるためです。もし毎回すべての領域を対象に掃除していたら、CPU使用率が増加し、アプリケーションの停止時間も長くなってしまいます。そこで、New領域とOld領域に分けることで、対象を絞り込んで効率的にGCを実行できるようにしています。さらにNew領域をEdenやSurvivorなどに細かく分割することで、オブジェクトが短命か長命かを判別しやすくなり、より適切に管理できます。次の「ガーベジコレクション」の章を読むと、これらの仕組みがさらにイメージしやすくなると思いますので、ぜひご参照ください。
ガベージコレクション(GC)
JVMが使用するメモリ領域の概要が分かったところで、次はガベージコレクション(GC)について理解を深めましょう。Javaの性能に大きく影響する要素のひとつがこのGCです。
JavaにおけるGCとは、Javaオブジェクトが使用していたメモリを自動的に解放する仕組みのことです。CやC++のようにプログラム内で明示的にメモリを解放する必要はありません。
GCには主に2種類あります。
- Minor GC (Scavenge GC):New領域のメモリを解放する処理で、通常数ミリ秒から数十ミリ秒で完了します。そのため、10秒おき程度に発生してもアプリケーションへの影響はほとんどありません。
- Full GC:New領域だけでなくOld領域も対象にメモリを解放する処理です。処理には数秒かかり、Stop the World(すべてのアプリケーションスレッドが停止する現象)が発生するため、アプリケーションの動作に影響を与える可能性があります。
したがって、JVMで利用するメモリ領域のサイズを適切にチューニングし、Full GCの発生頻度を抑えることが重要です。ミッションクリティカルシステムにおいては、多くても1時間に5〜10回程度に抑えることを推奨します。

Minor GC (Scavenge GC)
Minor GC の処理対象は、New領域にあるJavaオブジェクトです。Minor GCは、Eden領域がいっぱいになったタイミングで実行されます。処理の内容は以下の通りです。
- 不要なオブジェクトの破棄
- Eden領域やSurvivor領域に存在する不要なオブジェクトを解放します。
- オブジェクトの移動
- 生存しているオブジェクトは、まずEden領域から Survivor領域(To領域) に移動されます。
- Survivor領域内のオブジェクトは、必要に応じて From領域とTo領域 間で移動します。
- さらに長寿命のオブジェクトは Old領域 に昇格(プロモーション)します。
Minor GCは通常数ミリ秒から数十ミリ秒で完了するため、頻繁に発生してもアプリケーションへの影響はほとんどありません。

次に、Eden領域がいっぱいになった場合もMinor GCが実行されます。このときの処理の特徴は以下の通りです。
- 不要なオブジェクトの解放
- Edenに格納されている不要なJavaオブジェクトのメモリが解放されます。
- オブジェクトの移動
- 生存しているオブジェクトは、Edenから From領域 に移動します(前回のMinor GCではTo領域に移動していました)。
- もともとTo領域にあったオブジェクトについては、不要なものは解放され、まだ使用中のものは To領域からFrom領域 に移動されます。
このように、Minor GCではEdenとSurvivor(From/To)領域間でオブジェクトが入れ替わりながら、効率的にメモリ管理が行われます。

その後、Eden領域がいっぱいになるたびにMinor GCが実行されます。このとき、不要なオブジェクトは解放され、Survivor領域のFrom領域とTo領域間でオブジェクトの移動が繰り返されます。
生存オブジェクトの移動回数が、MaxTenuringThreshold(New領域に留まる最大GC回数)を超えると、そのオブジェクトは New領域からOld領域 に昇格(プロモーション)します。
この閾値を小さく設定しすぎると、Old領域に移動するオブジェクトの数が増え、結果としてFull GCの発生頻度も高くなってしまいます。したがって、適切な値にチューニングして、Full GCの負荷を抑えることが重要です。

Full GC
Full GCの処理対象領域には、New領域に加えてOld領域およびMetaspace領域があります。Full GCは様々なタイミングで発生しますが、概ねOld領域へのオブジェクト移動によってOld領域が不足した際に発生すると理解しておくとよいでしょう。

Full GCの発生頻度を抑えるには、各種Javaメモリ領域の容量を適切にチューニングするほか、アプリケーション側で不要なオブジェクトを長期間保持しない設計にすることが重要です。
まとめ
本記事は以上となります。説明した内容の要点をまとめます。
- JavaはJVMと呼ばれるソフトウェア上で動作し、異なるOSでも実行可能である。
- Javaが使用するメモリは、大きく分けてJavaヒープとネイティブメモリの2種類がある。
- JavaオブジェクトはJavaヒープ内の各領域で管理される。
- 不要になったJavaオブジェクトは、GC(Garbage Collection)実行時にメモリから解放される。
- GCにはScavenge GCとFull GCの2種類があり、性能への影響を避けるため、Full GCの回数は最小化する必要がある。
ここまで読んでいただきありがとうございました。性能関連の記事もいくつか書いていますので、ぜひ合わせてご覧ください。
【Apache】並列処理の仕組みとチューニング方法をまとめてみた。
【Tomcat】コネクションプールのチューニングによる流量制限の方法をまとめてみた。
【Tomcat】並列処理の仕組みと流量制限のためのチューニング方法
参考資料
https://www.scientecheasy.com/2021/03/java-bytecode.html/
https://www.hitachi.co.jp/Prod/comp/soft1/cosminexus/apserver/document/download/wp_fullgc.pdf
https://atmarkit.itmedia.co.jp/ait/articles/0504/02/news005.html


コメント