G1GCにおけるヒープ領域のメモリ管理の仕組み

ogp

こんにちは。カート決済部カート決済基盤ブロックの長沼です。先日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が発生します。

  1. YoungGC(Young-Only Collections)
  2. 混合GC(Mixed Collections)
  3. Full GC(Full Garbage Collections2

以降、順に処理の流れを説明します。

Young GC

Young GCは主にYoung世代のYoung領域・Eden領域のメモリを管理します。

まず、オブジェクトがEden領域に配置されます。

YoungGCの処理1 オブジェクト生成時

Eden領域がいっぱいになるとYoungGCが発生します。この時、未使用オブジェクトは解放されます。使用中オブジェクトはSurvivor領域へ移されます。

YoungGCの処理2 Survivor領域へ移動

空になったEden領域がいっぱいになると、再びYoungGCが発生します。この時、Survivor領域のオブジェクトが使用されていなければ解放されます。

YoungGCの処理3 Survivor領域の解放

Survivor領域がいっぱいになっていてEden領域から移動できない場合は直接Old領域へ移されます。

YoungGCの処理4 直接Old領域へ移動

7〜15回3のYoungGCを経てもSurvivor領域から解放されなかったオブジェクトはOld領域へ移されます。

YoungGCの処理5 閾値を超えてOld領域へ移動

混合GC

混合GCは主にOld領域のメモリを管理します。

ヒープ領域が逼迫してくると 混合GC が発生します。混合GCによりOld領域内の未使用オブジェクトが解放されます。

混合GCの処理

なお、混合GCの前には必ずYoungGCが実行されます。上の図はYoungGC後に混合GCが発生してOld領域を解放する様子を表しています。

Full GC

Full GCは混合GCが間に合わず、Old領域にて割り当て可能なメモリが不足した時に発生します。

FullGCの処理

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領域使用量が減少することを確認しました。

リリース後に慌てることがないよう、長時間シナリオテストを実施しましょう。

最後に

カート決済部では、仲間を募集しています。ご興味のある方は、こちらからご応募ください。

hrmos.co


  1. 実際のヒープ領域は均等のサイズに細かく区切られたリージョンから構成されていて、どの世代の管理を行なうかはリージョンごとに割り当てられます。Garbage-First (G1) Garbage Collector
  2. Webで情報を検索しやすいよう、括弧内は英語の正式名称を記載しています。Full Garbage Collections のみ Garbage が含まれていますが、これが正式名称です。HotSpot Virtual Machine Garbage Collection Tuning Guide
  3. 閾値は初期値が7回、最大15回です。-XX:InitialTenuringThreshold-XX:MaxTenuringThreshold オプションで変更できます。Java HotSpot VM Options
  4. 参考文献: Scott Oaks(著), アクロクエストテクノロジー株式会社(監修), 寺田 佳央(監修), 牧野 聡(翻訳), “Javaパフォーマンス”, オライリー・ジャパン, 2015/4/11, 227頁.
    • 1000はSoftRefLRUPolicyMSPerMBのデフォルト値です。-XX:SoftRefLRUPolicyMSPerMB オプションで変更できます。
    • 左辺と右辺で単位が異なりますが、そういうアルゴリズムです。
カテゴリー