- JAVA
- 技術 💻
Java OutOfMemoryErrorの原因と対処|メッセージ別に解説
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つに分かれます。
- 本当にデータ量が多い — 大きなファイルを一括で読み込む、DBの結果を全件リストに載せる、など
- メモリリーク — 使い終わったオブジェクトへの参照が残り続け、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点を見ます。
- どのクラスのインスタンスが、何個・何MBあるか(ヒストグラム)で犯人の当たりを付ける
- そのオブジェクトを誰が参照しているか(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期連続の黒字決算。