はじめに (対象読者・この記事でわかること)

この記事は、Javaのガベージコレクション(GC)について、その中でも特に重要な「Scavenge GC」と「Full GC」の違いを深く理解したいと考えているJava開発者の方、JVMのパフォーマンスチューニングに興味がある方を対象としています。

この記事を読むことで、以下の点が明確になります。 * Scavenge GCとFull GCがそれぞれどのような役割を担っているのか * それぞれのGCがどのような条件で発生するのか * GCの発生がアプリケーションのパフォーマンスにどのような影響を与えるのか * GCの挙動を理解することで、どのようなチューニングが可能になるのか

Javaアプリケーションの安定稼働やパフォーマンス向上には、GCの仕組みの理解が不可欠です。本記事を通して、GCに対する理解を深め、より効率的な開発・運用に繋げていきましょう。

Javaのガベージコレクション(GC)とは

Javaにおけるガベージコレクション(GC)とは、プログラムが使用しなくなったメモリ領域を自動的に解放する仕組みのことです。Javaでは、開発者が明示的にメモリ解放を行う必要がなく、GCがこれを担当することで、メモリリークなどのメモリ管理に関するバグを大幅に軽減し、開発効率を向上させています。

GCは、JVM (Java Virtual Machine) の重要な機能の一つであり、アプリケーションの実行中にバックグラウンドで動作します。GCの主な目的は、不要になったオブジェクトを検出し、それらが使用していたメモリを再利用可能にすることです。GCが効率的に動作しない場合、アプリケーションのパフォーマンス低下や、最悪の場合はOutOfMemoryErrorを引き起こす可能性があります。

JavaのGCには様々なアルゴリズムが存在しますが、その中でも特に基本的かつ重要な概念として、Scavenge GCとFull GCがあります。これらは、GCの実行範囲や頻度、そしてアプリケーションへの影響において、それぞれ異なる特徴を持っています。

Scavenge GCとFull GCの比較

JavaのGCは、メモリ領域を「Young世代」と「Old世代」に分けて管理することが一般的です。この世代別GCの考え方が、Scavenge GCとFull GCの挙動に大きく関わってきます。

Scavenge GC (Young GC)

Scavenge GCは、主にYoung世代のオブジェクトを対象としたGCです。Young世代は、さらに「Eden領域」「From Survivor領域」「To Survivor領域」の3つの領域に分かれています。

Scavenge GCの仕組み:

  1. オブジェクトの生成: 新しく生成されたオブジェクトのほとんどは、まずEden領域に配置されます。
  2. GCの発生: Eden領域がいっぱいになると、Scavenge GCが実行されます。
  3. オブジェクトのコピー: GCは、Eden領域とFrom Survivor領域にある到達可能なオブジェクト(まだ使用されているオブジェクト)を、To Survivor領域にコピーします。
  4. 世代移動: コピーされる際に、オブジェクトは生存回数(GCを生き残った回数)がカウントされます。生存回数が一定値を超えたオブジェクトは、Old世代へ移動(Promotion)します。
  5. 領域のクリア: コピーされなかったオブジェクト(到達不可能なオブジェクト)は解放され、Eden領域とFrom Survivor領域はクリアされます。To Survivor領域は、次のGCでFrom Survivor領域として使用されます。

Scavenge GCの特徴:

  • 頻繁に発生: Young世代はオブジェクトの生成が活発なため、Scavenge GCは比較的頻繁に発生します。
  • 短時間で完了: 対象となるメモリ領域がYoung世代に限定されているため、GCの実行時間は比較的短いです。
  • 「Stop-the-world」: GCの実行中は、アプリケーションのスレッドが一時的に停止します(Stop-the-world)。Scavenge GCは実行時間が短いため、この停止時間の影響は比較的小さいです。
  • 効率性: ほとんどのオブジェクトは短命であるという「弱参照の法則」に基づいて設計されており、Young世代のGCとしては非常に効率的です。

Full GC (Global GC)

Full GCは、Young世代とOld世代の両方、あるいはJVM全体を対象としたGCです。Old世代のオブジェクトが不足してきた場合や、Young世代からOld世代へのオブジェクトの移動が頻繁に発生し、Old世代がいっぱいになった場合に発生します。

Full GCの仕組み:

Full GCの具体的なアルゴリズムは、JVMでどのGCコレクター(Serial GC, Parallel GC, CMS, G1 GCなど)を使用しているかによって異なります。しかし、一般的には、以下のような処理を行います。

  1. 全領域の走査: アプリケーションが使用している全てのメモリ領域(Young世代、Old世代、メタスペースなど)を走査し、到達可能なオブジェクトを特定します。
  2. オブジェクトの整理・圧縮: 到達不可能なオブジェクトを解放し、メモリの断片化を解消するために、到達可能なオブジェクトをメモリの先頭に移動させる(圧縮)処理を行う場合があります。
  3. 領域の解放: 解放されたメモリ領域を再利用可能にします。

Full GCの特徴:

  • 発生頻度は低い: Young世代のGCに比べて、Full GCは発生頻度が低いです。
  • 実行時間が長い: 対象となるメモリ領域が広範囲にわたるため、GCの実行時間はScavenge GCよりも長くなります。
  • 「Stop-the-world」の影響が大きい: 実行時間が長いため、Stop-the-worldによるアプリケーションの停止時間も長くなり、パフォーマンスに顕著な影響を与える可能性があります。
  • メモリ不足の兆候: Full GCが頻繁に発生する場合、アプリケーションのメモリ使用量が多い、メモリリークがある、またはYoung世代からOld世代へのオブジェクトの移動が過剰であるといった問題の兆候である可能性があります。

違いのまとめ

特徴 Scavenge GC (Young GC) Full GC (Global GC)
対象領域 Young世代 Young世代、Old世代、メタスペースなど
発生頻度 高い 低い
実行時間 短い 長い
Stop-the-world 影響小 影響大
主な目的 短命なオブジェクトの解放 長命なオブジェクトの解放、メモリ整理
アルゴリズム コピー処理 マーク・スイープ、圧縮など

GCチューニングの視点

これらのGCの特性を理解することは、Javaアプリケーションのパフォーマンスチューニングにおいて非常に重要です。

  • Scavenge GCの最適化: Young世代のサイズを適切に設定することで、Scavenge GCの頻度と実行時間を調整できます。Young世代が小さすぎるとGCの頻度が増加し、大きすぎるとGCの実行時間が増加します。
  • Full GCの抑制: Full GCはパフォーマンスへの影響が大きいため、その発生を抑制することが重要です。
    • Old世代のサイズ調整: Old世代のサイズを適切に設定し、オブジェクトがOld世代に移動する前に十分な期間生存できるようにします。
    • オブジェクト生成の抑制: 不要なオブジェクトの生成を減らすことで、Young世代への負荷を軽減します。
    • メモリリークの排除: コード中のメモリリークを特定し、修正することで、不要なメモリ確保を防ぎます。
    • GCコレクターの選択: アプリケーションの特性やサーバー環境に合わせて、より効率的なGCコレクター(例: G1 GC, ZGC, Shenandoah GCなど)を選択することも有効な手段です。

JVMの -Xms (初期ヒープサイズ) や -Xmx (最大ヒープサイズ) といったオプションでヒープサイズを調整したり、GCログを分析したりすることで、GCの挙動を詳細に把握し、最適なチューニングを行うことができます。

まとめ

本記事では、Javaのガベージコレクションにおける「Scavenge GC」と「Full GC」について、それぞれの仕組み、特性、そして両者の違いを詳細に解説しました。

  • Scavenge GCはYoung世代を対象とし、短命なオブジェクトを効率的に解放しますが、頻繁に発生します。
  • Full GCはYoung世代とOld世代を含む広範囲を対象とし、長命なオブジェクトの解放やメモリ整理を行いますが、実行時間が長く、アプリケーションへの影響が大きくなります。

これらのGCの挙動を理解することは、Javaアプリケーションのパフォーマンスチューニングにおいて、GCの頻度や実行時間を最適化し、Stop-the-worldによる停止時間を最小限に抑えるための鍵となります。

今後は、様々なGCコレクター(G1 GC, ZGCなど)の具体的なチューニング方法や、GCログの分析方法について、より実践的な内容の記事も作成していく予定です。

参考資料