こんにちは。カート決済部カート決済基盤ブロックの長沼です。先日Javaアプリケーションをリリースしたのですが、リリース後にOld領域のメモリ使用量がわずかに増加し続ける現象が発生しました。本記事ではこの現象の調査にて得られた知見を共有します。
本記事で共有すること
GCの概要を理解するための前提知識を説明した後、G1GC(ガベージファースト・ガベージ・コレクタ)がメモリをどのように扱うか説明します。最後にメモリ使用量増加の原因となっていた ソフト参照
について説明します。
前提知識
まず、G1GCのメモリ管理方法を理解する際に前提となる事柄を説明します。
JVM(Java Virtual Machine)とは
Javaバイトコードを実行するための仮想環境です。JDK(Javaのプログラムを開発するためのソフトウェアパッケージ)に含まれています。
ヒープ領域とは
Javaプログラム実行時に生成したオブジェクトを割り当てるためのメモリ領域のことです。JVMのメモリ管理システムによって管理されます。
GC(ガベージ・コレクション)とは
プログラムが使用し終わったメモリ領域を探し、割り当てていたメモリを解放し他のプログラムで利用できるようにする機能を GC(ガベージ・コレクション)
と呼びます。
GCのアルゴリズム
GCのアルゴリズムにはいくつか種類があり、アプリケーション起動引数により切り替えできます。OpenJDK 17 64Bit Server VM
の場合はG1GCの他に シリアル・コレクタ
、パラレル・コレクタ
、Zガベージ・コレクタ
が選択できます。アプリケーションの特性やマシンリソースにより、最適なアルゴリズムは異なります。
G1GC(ガベージファースト・ガベージ・コレクタ)とは
大量のヒープ領域を最低限の停止時間で処理することを目標としたアルゴリズムです。マルチプロセッサ・マシンで動作することを前提として、アプリケーションの実行と同時に処理の一部を行うことでGCによる停止時間を短くします。
G1GCのメモリ管理
ヒープ領域の概念図
G1GCは世代別ガベージ・コレクションです。下の図はヒープ領域の概念図1です。
ヒープ領域は Young世代
と Old世代
に分類されます。Young世代はさらに Eden領域
と Survivor領域
に分類されます。Old世代は Old領域
のみとなります。
世代別領域に対するGCの基本処理
G1GCアルゴリズムでは3種類のGCが発生します。
- YoungGC(Young-Only Collections)
- 混合GC(Mixed Collections)
- Full GC(Full Garbage Collections2)
以降、順に処理の流れを説明します。
Young GC
Young GCは主にYoung世代のYoung領域・Eden領域のメモリを管理します。
まず、オブジェクトがEden領域に配置されます。
Eden領域がいっぱいになるとYoungGCが発生します。この時、未使用オブジェクトは解放されます。使用中オブジェクトはSurvivor領域へ移されます。
空になったEden領域がいっぱいになると、再びYoungGCが発生します。この時、Survivor領域のオブジェクトが使用されていなければ解放されます。
Survivor領域がいっぱいになっていてEden領域から移動できない場合は直接Old領域へ移されます。
7〜15回3のYoungGCを経てもSurvivor領域から解放されなかったオブジェクトはOld領域へ移されます。
混合GC
混合GCは主にOld領域のメモリを管理します。
ヒープ領域が逼迫してくると 混合GC
が発生します。混合GCによりOld領域内の未使用オブジェクトが解放されます。
なお、混合GCの前には必ずYoungGCが実行されます。上の図はYoungGC後に混合GCが発生してOld領域を解放する様子を表しています。
Full GC
Full GCは混合GCが間に合わず、Old領域にて割り当て可能なメモリが不足した時に発生します。
Full GCを行っている間、アプリケーションは完全に停止します。長時間停止してしまうことがあるためこの現象は STW(Stop the world)
と呼ばれ忌避されています。
ソフト参照
前半では世代別領域に対するGCの基本処理の流れを見てきました。ここからはメモリ使用量増加の原因となっていたソフト参照
について説明します。
Javaの参照
通常の方法でオブジェクトのインスタンスを生成した場合の参照は 強い参照
と呼ばれます。これまで説明してきたのは 強い参照
に対するGCの挙動でした。
Javaの参照は他に、ソフト参照
、弱い参照
、ファントム参照
があります。これらの参照を使用している場合、強い参照
の時とはGCの挙動が異なります。
ソフト参照とGC
java.lang.ref.SoftReferenceクラスを使用すると、ソフト参照
の仕組みを利用できます。ソフト参照はヒープ領域が逼迫されるとGC対象となります。キャッシュを実装するために使用されることが多いです。
強い参照の場合、未使用であることがメモリ解放の対象となる条件でした。ソフト参照は最長不使用(LRU)アルゴリズムにより解放対象を決定します。具体的には下記の式4に該当するものを解放対象とします。
現在時刻(ミリ秒) - 最終使用時刻(ミリ秒) > ヒープ内空きメモリ(MB) * 1000
なお、ソフト参照のメモリ解放には複数回混合GCが必要になります。
まとめ
ソフト参照はメモリ解放となる条件が強い参照と異なるため、アプリケーションの挙動を確認するためには長時間シナリオのテストが必要です。本記事の調査では開発環境にて70時間の負荷試験を行い、複数回の混合GC発生後にOld領域使用量が減少することを確認しました。
リリース後に慌てることがないよう、長時間シナリオテストを実施しましょう。
最後に
カート決済部では、仲間を募集しています。ご興味のある方は、こちらからご応募ください。
- 実際のヒープ領域は均等のサイズに細かく区切られたリージョンから構成されていて、どの世代の管理を行なうかはリージョンごとに割り当てられます。Garbage-First (G1) Garbage Collector↩
-
Webで情報を検索しやすいよう、括弧内は英語の正式名称を記載しています。
Full Garbage Collections
のみGarbage
が含まれていますが、これが正式名称です。HotSpot Virtual Machine Garbage Collection Tuning Guide↩ -
閾値は初期値が7回、最大15回です。
-XX:InitialTenuringThreshold
や-XX:MaxTenuringThreshold
オプションで変更できます。Java HotSpot VM Options↩ -
参考文献: Scott Oaks(著), アクロクエストテクノロジー株式会社(監修), 寺田 佳央(監修), 牧野 聡(翻訳), “Javaパフォーマンス”, オライリー・ジャパン, 2015/4/11, 227頁.
- 1000はSoftRefLRUPolicyMSPerMBのデフォルト値です。
-XX:SoftRefLRUPolicyMSPerMB
オプションで変更できます。 - 左辺と右辺で単位が異なりますが、そういうアルゴリズムです。
- 1000はSoftRefLRUPolicyMSPerMBのデフォルト値です。