「JVM」タグアーカイブ

Javaメモリ管理の仕組み:ガーベジコレクションとヒープの基本

1. Javaのメモリ管理とは?

Javaはプログラマーが手動でメモリを解放しなくても良い言語です。
C・C++のように free() を使う必要はありません。

Javaでは、必要なくなったオブジェクトを自動で回収(解放)する仕組みが備わっています。
これを**ガーベジコレクション(Garbage Collection, GC)**と呼びます。

「解放漏れによるメモリリークが発生しづらい」
→ Javaが幅広く使われる理由の1つ


2. Javaのメモリ領域:ヒープとスタック

Javaアプリのメモリは主に次の2領域で管理されます。

領域役割特徴
ヒープ(Heap)オブジェクト、配列を格納GCの対象
スタック(Stack)メソッド実行中の変数・参照メソッド終了で自動解放

ヒープ領域の構造(JDK8以降)

領域内容
Young Generation新規オブジェクト領域(Eden、Survivor)
Old Generation長生きするオブジェクト領域

多くのオブジェクトはすぐ不要になる → Young に多く配置するのが効率的


3. ガーベジコレクションの動き

ガーベジコレクションは、参照されなくなったオブジェクトを検出・削除します。

処理の流れ

  1. オブジェクトを生成(ヒープに配置)

  2. 参照が切れる or 到達不能になる

  3. GCが不要オブジェクトを回収

GCアルゴリズム(代表)

名称特徴
Mark and Sweep到達可能オブジェクトに印を付け、残りを削除
Copying生きているオブジェクトを別領域に移動して残りを破棄
Generational GC世代(Young/Old)でGC動作を変える効率化方式

4. Javaの主要GC方式(JDKバージョン別)

GC機能特徴対象バージョン
Serial GCシングルスレッド、単純設計軽量アプリ
Parallel GC並列処理で高速デフォルト(Java8)
G1 GC大規模ヒープ向け、低停止時間Java9以降推奨
ZGC超低遅延GC、数百GB〜TB向けJava15以降
ShenandoahRedHat版、低遅延OpenJDK系

5. よくあるメモリ関連エラー

java.lang.OutOfMemoryError

ヒープ不足で発生
→ ヒープ拡張 or メモリリーク調査

StackOverflowError

再帰のしすぎなどでスタック溢れ

❌ メモリリーク

Javaでも発生します(例:Listにaddしっぱなし)


6. メモリ管理・GCチューニングのポイント

対策内容
不要な参照を早く消すローカル変数は小スコープ
大規模データは逐次処理巨大Listを避けIterator活用
WeakReference活用キャッシュ管理時に便利
GCログ・ツール利用-Xmx設定、VisualVM/FlightRecorder

JVMオプション例


7. まとめ

ポイント内容
Javaは自動メモリ管理ガーベジコレクションが解放処理
ヒープが主な領域Young/Old世代で効率化
GC方式は進化中G1GC・ZGCが主流
最適化の余地あり適切なコーディング+JVM設定

「自動だから安心」ではなく、仕組み理解でパフォーマンス向上!

JavaのGC(Garbage Collection)とは?仕組みと注意点

GCとは何か

Java で開発をしていると、よく耳にする「GC(Garbage Collection)」。
これは 不要になったオブジェクトを自動で回収してメモリを解放する仕組み のことです。C言語のように手動で free() を呼ぶ必要はなく、Java VM が裏側でメモリ管理を行います。

 

ざっくり構造・最近のGC

  • 世代別回収:Eden/Survivor(若世代)→ Old(老世代)

  • Minor GC:Edenが埋まったら短命オブジェクト中心に回収

  • Major/Full GC:Oldが逼迫、断片化、クラス/メタ領域逼迫などで広域回収

  • 既定GC:G1GC(Java 9+)。低停止要求は ZGC / Shenandoah も選択肢

主なトリガ

  • Eden満杯(Minor) / Old高水準(Major)

  • 巨大配列(Humongous)割当て(G1)

  • System.gc() 明示呼び出し

  • メタスペース/オフヒープ圧迫(DirectByteBuffer/JNI など)


“悪い例 → 良い例”で学ぶメモリ/GC対策

1) 無制限キャッシュ(静的Map地獄)

悪い例

良い例(上限+期限+統計)

ポイント:上限なしは必ずOldを膨らませる。キャッシュは 容量・期限・エビクションを設計。


2) リスナ/コールバック未解除

悪い例

良い例(ライフサイクルで必ず解除 / AutoCloseable化)

補足WeakReference リスナはイベント強度低下意図せぬ解放のリスク。基本は明示解除


3) ThreadLocal の放置(プールスレッドに張り付く)

悪い例

良い例(finallyで確実に除去)

ポイントスレッドプール=長寿命remove() を忘れると実質グローバル保持


4) System.gc() 乱用

悪い例

良い例


5) ラムダ/内部クラスが外側(巨大オブジェクト)をキャプチャ

悪い例

良い例(必要最小限のデータだけ渡す・static化)

ポイントキャプチャ=保持。意図せず大物を延命していないか疑う。


6) ループ内の大量一時オブジェクト

悪い例

良い例(StringBuilder再利用・ボクシング回避)


7) finalize/Cleaner頼み(遅延・不確実)

悪い例

良い例(確実な即時解放) 


8) クラスローダ・アプリ再デプロイ時のリーク

悪い例

良い例(クラスローダ境界を越える参照を断つ)

 

9) 巨大配列・Humongous割当ての長期保持(G1)

悪い例

良い例(分割・ストリーミング・寿命短縮)

ポイント:巨大ブロックは断片化回収コスト増の温床。


10) 無制限のキュー/バッファ

悪い例

良い例(有界+バックプレッシャ)


GCログ・計測の始め方(JDK 9+)

  • まずはコードの割当て削減 → その後にヒープ/GC調整

  • 監視:jcmd <pid> GC.heap_info / jstat -gc <pid> 1000

  • ボトルネック特定:JFR(Java Flight Recorder) で割当てホットスポットを把握

  • 必要なら ZGC/Shenandoah も評価(レイテンシ目標に応じて)


実務チェックリスト(配布推奨)

  1. System.gc() を禁止/抑制

  2. キャッシュ・キューは有界+期限

  3. ThreadLocal は finally で remove

  4. リスナ/コールバックは確実に解除(AutoCloseable化が効く)

  5. ループ内の一時オブジェクトを減らす(Builder再利用/ボクシング回避)

  6. 巨大配列は分割・短命化

  7. クラスローダ境界を跨ぐ静的参照禁止、Executor停止・ドライバ解除

  8. try-with-resourcesでオフヒープ即時解放

  9. GCログ/JFRで事実ベースに調整

  10. 目標停止時間(例:MaxGCPauseMillis)を定めて検証


まとめ

GCは“自動”でも“万能”ではありません。
「GCが働きやすいコード」(不要参照を残さない・波及して大物を掴ませない・ピークメモリを避ける)を心がけ、ログ/計測で改善ループを回すのが最短距離です。