メモリが増え続ける

「リークしている」と決めつける前に、リーク・断片化・キャッシュ・計測ミスの4つを切り分けられるようになる。どの領域が伸びているかをRSSとヒープの差から確定させ、犯人を絞り込める。

応用トラブルシュートメモリGCリークプロファイリング最終更新: 2026-07-29
3つの要点
TL;DR
  1. 増え続けている=リークとは限らない。上限のないキャッシュ、アロケータの断片化、そしてそもそもの計測対象の誤りが、同じ「右肩上がり」に見える。
  2. 最初の分岐はRSSとヒープ使用量の比較。両方伸びるならヒープ内のリーク、RSSだけ伸びるならネイティブ領域・断片化・スレッド/FDの積み上がりを疑う。
  3. 決め手はスナップショットの差分。負荷をかけてGC後の状態を2回取り、増えたオブジェクトの型と保持経路を見る。型より保持経路(誰が掴んでいるか)が答えになる。

メモリ使用量のグラフが右肩上がりで、放っておくとOOMKilledで落ちる。よくある反応は「メモリリークだ」と断定してプロファイラを起動することですが、その断定が既に1つの仮説です。同じ右肩上がりのグラフは、リーク以外に少なくとも3つの原因から生まれます。切り分けずにプロファイラを覗いても、大量のオブジェクト一覧を前に途方に暮れるだけです。

4つの候補を先に並べる

横にスクロール

RSSとheapの差から増加を四つの候補へ分ける症状から観測と仮説分岐、復旧判定までを示す図
症状を観測値で分岐し、次の確認と再発防止へつなげる診断順序を整理します。
候補何が起きているか見分ける手がかり
本物のリーク不要になったオブジェクトが参照され続け解放されないGC後も減らない。時間に比例して単調増加
上限のないキャッシュ設計通りに保持しているが上限が無いGC後も減らないが、キー数と相関する。実装を読めば分かる
アロケータの断片化解放済みだがOSへ返却されず、確保済み領域が広がるヒープ使用量は横ばいなのにRSSだけ伸びる
計測対象の誤りそもそも見ている数字が用途違いfree の used を見ている、キャッシュを含む値を見ている等
いちばん多いのは4番(計測対象の誤り)

「メモリが逼迫している」という報告の相当数は、free -hused や、ページキャッシュを含む値を見ていることに起因します。Linux はファイルを読むたびに空きメモリをページキャッシュに使いますが、これは必要になれば即座に解放されるので逼迫ではありません。見るべきは available です。この読み方はfree -h の出力解剖で詳しく扱っています。まずここを潰してから先へ進んでください。

最初の分岐: RSSとヒープを並べる

決定的な分岐は、プロセス全体のメモリ(RSS)と、ランタイムが管理するヒープ使用量を並べて見ることです。この2つの動きの違いが、犯人の居場所をほぼ確定させます。

RSS ↑  かつ  ヒープ使用量 ↑
  → ヒープ内で参照が残っている
    =本物のリーク、または上限のないキャッシュ

RSS ↑  だが  ヒープ使用量は横ばい
  → ヒープの外が伸びている
    =断片化/ネイティブメモリ(バッファ・拡張ライブラリ)
      /スレッド数の増加/FDやソケットの積み上がり

ヒープ使用量は各ランタイムの標準的な手段で取れます(JVM なら GC ログや jcmd GC.heap_info、Node.js なら process.memoryUsage()heapUsed、Go なら runtime.MemStats)。GC 直後の値で比べるのが要点です。GC 前の値はゴミを含むので、増えているように見えて当然です。

「GC後の下限」が上がっているかを見る

メモリのグラフはのこぎり波になります。ここで見るべきは山の高さではなく、谷の高さ(GC後の下限)が回を追うごとに上がっているかです。谷が上がり続けているなら、解放されない何かが確実に積み上がっています。谷が一定なら、山が高くても健全な使い方です。この判定は、プロファイラを起動する前に無料でできます。

分岐A: ヒープが伸びている

リークかキャッシュかを決めます。両者は「参照が残っている」点で同一で、意図的かどうかだけが違います

手順1: ヒープスナップショットの差分を取る。 負荷をかけ、GC を強制し、2点でスナップショットを取って差分を見ます。増えているオブジェクトの型が一覧されますが、ここで注意があります。

手順2: 型ではなく保持経路を見る。 増えている型が Stringbyte[] であることはよくありますが、それは答えではありません。知りたいのはその文字列を誰が掴んでいるかです。プロファイラの「保持経路(retaining path)」「GC ルートまでの参照」を辿ります。ここに出てくるコレクションが犯人です。典型例を挙げます。

  • 上限のない Map をキャッシュに使っている。 追加はするが削除しない。キーがユーザーIDやリクエストIDなら無限に増えます。
  • リスナー・コールバックの登録解除漏れ。 登録したまま解除しないと、そのオブジェクトと、それが参照するもの全部が生き続けます。
  • スレッドローカル変数の残留。 スレッドプールではスレッドが使い回されるため、処理後にクリアしないと値が残り続けます。
  • クロージャが意図せず大きなオブジェクトを掴んでいる。 小さな値だけ使うつもりが、スコープごと保持されている。

手順3: キャッシュなら上限を入れて終わり。 意図的な保持だったなら、それはバグではなく設計の欠落です。件数上限とエビクション方針を入れます(挙動はLRUキャッシュ可視化で確認できます)。

分岐B: RSSだけ伸びている

ヒープが横ばいなのに RSS が伸びる場合、疑うのは次の4つです。

1. アロケータの断片化。 大小さまざまなサイズの確保と解放を繰り返すと、解放済みの隙間が使えないまま残り、アロケータは OS へ返却できません。「使っていないのに減らない」の正体です。長時間稼働のプロセスで徐々に効いてきます。

2. ネイティブメモリ。 ランタイムのヒープ外で確保される領域——ダイレクトバッファ、圧縮/画像/暗号ライブラリの内部バッファ、JIT のコードキャッシュ、メタスペース。ヒーププロファイラには映りません。

3. スレッド数の増加。 スレッドは1本ごとにスタック領域(既定で 1MB 前後)を持ちます。スレッドが解放されずに増え続ければ、それだけで RSS は伸びます。スレッド数のグラフを重ねると一発で分かります。

4. ファイルディスクリプタ・ソケットの積み上がり。 直接のメモリ量は小さいものの、これが増え続けているなら「解放漏れが起きている」という同じ根本原因のより見つけやすい兆候です。先にEMFILEとして顕在化することもあります。

# 伸びているのがどれかを同時に並べる
ps -o pid,rss,vsz,nlwp -p <PID>     # RSS / 仮想 / スレッド数
ls /proc/<PID>/fd | wc -l            # FD数

増加の「形」から絞る

グラフの形状にも情報があります。

増え方示唆疑う先
時間に正確に比例定期処理のたびに積むcronやスケジューラ、定期ポーリングの登録漏れ
リクエスト数に比例1リクエストにつき一定量残るリクエストスコープの解放漏れ、キャッシュキーの肥大
階段状に跳ねる特定の操作でまとめて確保バッチ、大きなファイル読み込み、一括クエリ
再起動直後だけ急増し横ばいウォームアップ健全。リークではない

最後の行が重要です。起動直後に急に増えてから平らになるのは正常です。JIT のウォームアップ、接続プールの確立、キャッシュの初期充填で、そこを切り取ると「増え続けている」ように見えます。判定するには十分長い観測窓が要ります。

まとめ

  • 右肩上がり=リークではない。リーク・上限のないキャッシュ・断片化・計測対象の誤りの4候補をまず並べる。最頻は計測対象の誤りで、freeused ではなく available を見る。
  • 最初の分岐は RSS とヒープ使用量を並べること。両方伸びればヒープ内、RSS だけならヒープ外。
  • グラフは山ではなく谷(GC後の下限)が上がっているかを見る。プロファイラを起動する前に無料でできる判定。
  • ヒープ内なら型ではなく保持経路を辿る。犯人は上限のない Map・解除漏れのリスナー・スレッドローカル・クロージャのいずれかであることが多い。
  • RSS だけなら断片化・ネイティブメモリ・スレッド数・FD を並べて確認する。スレッド数のグラフは特に見落とされやすい。
  • 起動直後の急増と横ばいはウォームアップであって異常ではない。判定には十分長い観測窓が要る。

トラブルシュート実戦の記事ガイド

メモリが増え続けるを実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

トラブルシュート

比較で見る軸

難易度: advanced / カテゴリ: トラブルシュート実戦 / タグ数: 5

導入後に効く点

最初の分岐はRSSとヒープ使用量の比較。両方伸びるならヒープ内のリーク、RSSだけ伸びるならネイティブ領域・断片化・スレッド/FDの積み上がりを疑う。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
トラブルシュート実戦
タグ数
5

判断チェックリスト

  • 自社の用途が「トラブルシュート / メモリ」に近いか確認する。
  • 強みである「増え続けている=リークとは限らない。上限のないキャッシュ、アロケータの断片化、そしてそもそもの計測対象の誤りが、同じ「右肩上がり」に見える。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

トラブルシュートメモリGCリークプロファイリング