20
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Javaのメモリ管理をざっくり理解する(Out-of-Memoryに至る流れ)

20
Last updated at Posted at 2026-02-16

業務でもJavaアプリの性能測定や負荷試験などを実施したり、メモリを増やしたりすることがあると思います。
でもそもそも「メモリを増やす」という結論へと至る前に、メモリ不足エラー(Out-of-Memory:OOM)が発生する流れを理解しておくべきと思い、本記事に改めてまとめさせていただきます。

全体構造

OSメモリ
└─ JVMプロセス
├─ Heap
│ ├─ Young
│ └─ Old
├─ Metaspace
├─ Thread Stack
├─ Direct Memory
└─ Code Cache

重要ポイント:
 ・-XmxはHeapのみ
 ・GC対象は基本的にHeap
 ・OOMはHeap以外でも発生する

領域 主な役割 確保タイミング 解放タイミング GC対象 補足ポイント
Heap(Young/Old) newしたオブジェクト格納 JVM起動時に-Xms分確保、必要に応じて-Xmxまで拡張 GCで不要オブジェクト回収(ただしOSへ即返却とは限らない) GCの主戦場。Old使用率が重要指標
Metaspace クラス情報・静的変数・メソッド情報 クラスロード時に拡張 クラスローダがGCされると解放 × 再デプロイ多いと肥大化
Thread Stack ローカル変数・メソッド呼び出し履歴 スレッド生成時(-Xss分) スレッド終了時 × スレッド数×Xssで計算される
Direct Memory NIOバッファなど allocateDirect使用時 対応オブジェクトGC後に解放(遅延あり) ×(ヒープ外) Heap空いていてもOOMあり
Code Cache JITコンパイル済コード 実行中にJITが生成 基本解放されない(JVM終了時) × 枯渇すると性能低下

フロー①:アプリ起動時

 1.OSがJVMプロセス生成
 2.Heapを-Xms分確保
 3.Metaspace初期確保
 4.メインスレッド生成(Thread Stack確保)
 5.クラスロード(Metaspace使用)
 6.JIT開始(Code Cache使用)

この時点で、Heap以外もすでに消費している。

フロー②:通常処理

new → Young(Eden)へ配置
Edenが満杯になるとMinor GC発生。

フロー③:Minor GC

対象:Youngのみ
流れ:
 1.参照可能オブジェクト探索
 2.生存オブジェクトをSurvivorへコピー
 3.一定回数生存でOldへ昇格
 4.不要オブジェクト破棄

ここで発生するのがStop The World(STW)
 →日本語で言うと:「全アプリケーションスレッド一時停止」
GC中はユーザ処理が止まる。

フロー④:Old増加

長寿命オブジェクトが溜まる。
 ・キャッシュ
 ・セッション
 ・静的保持

フロー⑤:Mixed GC(G1の場合)

Oldの一部も掃除。それでも減らないとフロー⑥へ移行

フロー⑥:Full GC

対象:Young + Old全体
処理:
 1.生存確認(Mark)
 2.削除(Sweep)
 3.圧縮(Compact)

特徴:
 ✔ STW長時間
 ✔ CPU高負荷
 ✔ サーバレスポンス低下

フロー⑦:減らない場合、OOM発生

Full GC後もOld使用率が高い場合、空き確保不能。
OutOfMemoryError発生

全体まとめフロー

起動

Heap/Metaspace確保

オブジェクト生成(Young)

Minor GC

Old蓄積

Mixed GC

Full GC

減らない

OOM

SEが本当に気をつけるべきこと

本質は「どのオブジェクトが、なぜ生き続けているか」
これを構造で理解できるかどうかが、OOM対応力の差になります。

しかしJavaアプリケーションでメモリ不足やGC多発が発生したとき、
まず検討されがちなのが次の対応です。
 ・Xmxを増やす
 ・サーバのメモリを増設する
 ・インスタンスサイズを上げる

確かに一時的な延命にはなります。
しかし多くの場合、それは根本解決ではありません。
以降に「不要なオブジェクトを長生きさせない」ためにはコーディングで何を気をつけるべきかを併せて列挙します。

①キャッシュの無制限保持

static Map cache = new HashMap<>();

 ・上限なし
 ・削除ロジックなし
 ・有効期限なし

これは Old世代肥大化確定コースの利用につながります。
対策:
 ・サイズ上限
 ・TTL設定
 ・LRU
 ・Caffeineなど利用

② セッションに巨大オブジェクト保持

session.setAttribute("userInfo", hugeObject);

セッションは基本長寿命。
 ・ファイル
 ・大量データList
 ・Entity丸ごと
これらを入れると Old固定化されてしまう。
原則:
 ・IDだけ持つ
 ・必要時DB再取得
 ・軽量DTO化

③ 静的保持(static確保大量)

public static List dataList = new ArrayList<>();

staticは「アプリ終了まで解放されない」つまり「GC対象にならない」(=メモリリークと同義)。特に危険なのは
 ・staticコレクション
 ・staticシングルトン
 ・static ThreadLocal

まとめ

意識 理由
使い終わったら参照を切る 参照がある限りGCされない
スコープを狭くする メソッド内変数は自然に消える
staticは極力使わない ほぼ解放されない
キャッシュは設計する なんとなく保持は危険な為

Javaメモリ設計で最も重要なのは「どこに確保されるか」ではなく「どれだけ長生きさせてしまうか」。
ここを意識できるSEは、GCログを見なくてもトラブルを未然に防げるかもしれません。

20
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?