OutOfMemoryError(OOM)は、JVMがメモリを確保できなかったときに発生するエラーです。ただし「ヒープが足りない」とは限りません。エラーメッセージの後半に、どのメモリ領域が枯渇したかが書かれていて、原因も対処もメッセージごとに異なります。

「とりあえず -Xmx を増やす」で一時的に収まっても、原因がメモリリークなら時間の問題で再発します。この記事では、メッセージ別の原因と対処、調査の手順、コンテナ環境で起きやすい設定の落とし穴までを、1本にまとめて解説します。

まず、エラーメッセージの後半を読む

java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: unable to create native thread

「OutOfMemoryError:」の後ろが、枯渇した領域です。JVMのメモリはヒープだけでなく、クラス情報を置くメタスペース、スレッドごとのスタック、ヒープ外に確保されるダイレクトバッファなどに分かれていて、それぞれが別々に枯渇し、別々のメッセージを出します。

メッセージ 枯渇した場所 主な原因
Java heap space ヒープ データ量が多い/メモリリーク
GC overhead limit exceeded ヒープ(GCが回収できない) メモリリーク/ヒープ不足
Metaspace クラス情報の領域 クラスの動的生成・再デプロイの繰り返し
unable to create native thread OSのスレッド・メモリ スレッドの作りすぎ/OSの上限
Direct buffer memory ヒープ外のバッファ NIO・ネットワークライブラリのバッファ
Requested array size exceeds VM limit 配列サイズの上限 極端に大きな配列の確保

メッセージ別の原因と対処

Java heap space

ヒープに新しいオブジェクトを置く場所がなくなった状態です。原因は大きく2つに分かれます。

  1. 本当にデータ量が多い — 大きなファイルを一括で読み込む、DBの結果を全件リストに載せる、など
  2. メモリリーク — 使い終わったオブジェクトへの参照が残り続け、GCが回収できない

見分け方は、GC後のヒープ使用量の推移です。GC後の使用量が右肩上がりを続けているならリーク、一定の水準で上下しているなら処理のピーク時にヒープが足りていない可能性が高いと判断できます。

対処 - データ量が原因なら、一括読み込みをやめてストリーム処理・ページング・チャンク処理に変える - リークが原因なら、ヒープダンプで参照を持ち続けている箇所を特定して直す(後述) - 処理の性質として必要な量なら、-Xmx でヒープ上限を引き上げる

Javaでよくあるリークの型は、staticなコレクションに追加し続けて削除しない、リスナーの登録解除忘れ、キャッシュに上限や期限がない、の3つです。GCは「参照されていないもの」しか回収しないため、参照を握っている限り、使っていなくてもメモリは解放されません。

GC overhead limit exceeded

GCに処理時間のほとんどを使っているのに、ほとんどメモリを回収できていない状態で出るエラーです(主にParallel GCで発生)。ヒープがほぼ満杯でGCが空回りしている状態なので、原因と対処は「Java heap space」とほぼ同じです。リークの有無をまず確認します。

Metaspace

クラスの定義情報を置く領域(メタスペース)が枯渇した状態です。通常のアプリではクラス数は一定なので、発生するのは主に次のケースです。

  • 実行時にクラスを動的に生成し続けている(プロキシ生成・スクリプトエンジン・一部のフレームワーク)
  • アプリケーションサーバーで再デプロイを繰り返し、古いクラスローダーが解放されていない

対処:動的生成の仕組みを見直す、再デプロイ時のクラスローダーリーク(スレッドやThreadLocalが古いクラスを参照し続けている等)を解消する、必要なら -XX:MaxMetaspaceSize で上限を明示して早期に検知できるようにします。

unable to create native thread

新しいスレッドを作れなかった状態です。ヒープとは関係なく、次のどちらかが原因です。

  • スレッドを作りすぎている — リクエストごとに新規スレッドを作る、スレッドプールを使っていない、など
  • OS側の上限に達している — プロセスあたりのスレッド数の上限、コンテナのプロセス数制限(pids制限)、スレッドスタック用のメモリ不足

対処:まず jcmd <pid> Thread.print などでスレッド数と内訳を確認します。スレッドが際限なく増えているならスレッドプールで本数を制御するのが根本対策です。本数が妥当ならOSやコンテナの上限(ulimit -u やpids制限)を確認します。スレッドのスタックサイズ(-Xss)を小さくする方法もありますが、深い再帰でStackOverflowErrorを招くので慎重に。

Direct buffer memory

ヒープ外に確保するダイレクトバッファ(NIOや、ネットワーク系ライブラリが内部で使うもの)が上限に達した状態です。ヒープにいくら空きがあっても発生します。

対処:バッファの解放漏れ(ライブラリの使い方)を確認し、必要に応じて -XX:MaxDirectMemorySize で上限を設定します。

調査の基本:ヒープダンプを取る

リークの特定は、推測ではなくヒープダンプ(メモリの中身のスナップショット)で行います。

OOMの瞬間に自動で取得する(推奨)

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/app/heapdump.hprof

本番のJVM起動オプションに入れておくと、OOMが起きた瞬間のメモリ状態が保存されます。再現しにくい障害の調査で、最も重要な設定です。ダンプはヒープと同程度のサイズになるので、保存先の空き容量も確認しておきます。

動いているプロセスから取得する

jcmd <pid> GC.heap_dump /tmp/heapdump.hprof

ダンプの見方

Eclipse MATなどの解析ツールで開き、次の2点を見ます。

  1. どのクラスのインスタンスが、何個・何MBあるか(ヒストグラム)で犯人の当たりを付ける
  2. そのオブジェクトを誰が参照しているか(GCルートまでの経路)で、参照を握り続けている箇所を特定する

時間を空けて2回取得し、増え続けているものを比べると、リークの特定がぐっと速くなります。

コンテナで起きやすい設定の落とし穴

現在のJVMは、コンテナのメモリ制限を認識してヒープの最大値を自動で決めます。既定値はコンテナメモリの25%(-XX:MaxRAMPercentage=25)です。

  • 2GBのコンテナで何も指定しないと、ヒープ上限は約512MB — 「メモリを十分渡したのにOOMになる」の典型的な原因です
  • 逆に、-Xmxをコンテナ上限ぎりぎりにすると危険 — JVMはヒープ外(メタスペース・スレッドスタック・ダイレクトバッファ・JIT領域など)もメモリを使うため、合計がコンテナ上限を超えるとコンテナごと強制終了されます

コンテナでは、-XX:MaxRAMPercentage=70 前後から始めて、実際の使用量を見ながら調整するのが一般的です。ヒープ+ヒープ外+余裕の合計で考えるのがポイントです。

OutOfMemoryErrorとOOM Killerは別物

アプリのログに何も残らず、プロセスが突然消えていた場合は、JavaのOutOfMemoryErrorではなくLinuxのOOM Killer(OS全体のメモリが枯渇したときに、OSがプロセスを強制終了する仕組み)の可能性があります。

dmesg やシステムログに「Out of memory: Killed process」の記録がないか確認します。この場合、JVMの中ではなく、サーバーやコンテナ全体のメモリ配分を見直す必要があります。

再発防止のチェックリスト

  • [ ] -XX:+HeapDumpOnOutOfMemoryError を本番の起動オプションに入れた
  • [ ] GC後のヒープ使用量を監視している(右肩上がりにアラート)
  • [ ] キャッシュやstaticコレクションに上限・期限がある
  • [ ] スレッドはスレッドプールで本数を制御している
  • [ ] コンテナのメモリ上限と、ヒープ設定(MaxRAMPercentage等)の関係を確認した
  • [ ] 大量データはストリーム処理・ページングで扱っている

よくある質問

Q. -Xmxを増やせば解決しますか? A. データ量が原因なら解決します。メモリリークが原因なら、発生までの時間が延びるだけで再発します。まずGC後のヒープ使用量の推移を確認してください。

Q. ヒープに空きがあるのにOutOfMemoryErrorが出ます。 A. メッセージを確認してください。Metaspace・unable to create native thread・Direct buffer memoryは、ヒープ以外の領域の枯渇です。

Q. System.gc()を呼べば防げますか? A. 防げません。System.gc()はGCの実行を「お願い」するだけで、参照が残っているオブジェクトは回収されません。

Q. 本番でヒープダンプを取っても大丈夫ですか? A. 取得中はアプリが一時停止し、ヒープと同程度のファイルが作られます。取得のタイミングと保存先の容量に注意してください。OOM時の自動取得は、停止直前の状態を残せるので特に有効です。

まとめ

  • OutOfMemoryErrorは、メッセージの後半で「どの領域が枯渇したか」を読むところから
  • heap spaceは「データ量」か「リーク」か、GC後のヒープ使用量の推移で見分ける
  • ヒープ以外の領域(Metaspace・スレッド・ダイレクトバッファ)は、それぞれ原因が違う
  • 調査の基本はヒープダンプ。OOM時の自動取得を本番に入れておく
  • コンテナでは既定のヒープ上限(25%)と、ヒープ外のメモリを必ず考慮する

株式会社RJCでは、金融業界向けのJava 21プロジェクトなど、長時間稼働する業務システムの開発に携わっています。社内のJava探求グループでも、こうした「動き続けるシステムを支える知識」を共有しています。

▶ RJC丸わかり https://www.r-jc.jp/guide/?mediacode=02-00001-003

▶ エントリーはこちら(1〜2分で完了します) https://www.r-jc.jp/recruitment/?mediacode=02-00001-003

株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?