Kubernetes(K8s)上でJavaアプリを運用していると、 java.lang.OutOfMemoryErrorが出ないまま Pod が突然再起動する という現象に遭遇することがあります。 これは多くの場合、Kubernetes特有のメモリ制御(cgroup)とJavaメモリ設計の不整合が原因です。
本記事では、KubernetesでJavaがOOMKilledされる内部仕組みを整理し、 どこを見れば原因特定でき、どう設計すべきかを実務目線で解説します。
この記事でわかること
KubernetesのOOMKilledがどのように発生するのか、Javaのどのメモリが制限対象になるのかを理解できます。 また、OOMKilled時の確認ポイントと、事故を防ぐための設計・設定指針がわかります。
OOMKilledとは何か
OOMKilledとは、コンテナが割り当てられたメモリ上限を超えた瞬間に、Kubernetesがプロセスを強制終了する動作です。
重要なのは、これはJVMのOOMではなく、Linuxカーネル(cgroup)によるOOMである点です。
Kubernetesのメモリ制御の仕組み
Kubernetesでは、Pod / Container に以下の2つのメモリ値を設定できます。
resources:
requests:
memory: "2Gi"
limits:
memory: "4Gi"
- requests:スケジューリングの基準
- limits:超えた瞬間にOOMKilled
Javaプロセスは limits による cgroup メモリ制限を直接受けます。
Javaが使うメモリの内訳
Kubernetesは JVM の内部構造を理解しません。 以下のすべての合計が limits を超えると OOMKilled されます。
- Java Heap(-Xmx)
- Metaspace
- Direct Memory(NIO / Netty)
- Thread Stack(スレッド数 × Xss)
- JNI / ネイティブメモリ
-Xmx は全体の一部に過ぎない点が最大の落とし穴です。
よくあるOOMKilledパターン
① Xmxをlimitsと同じにしている
limits:
memory: "4Gi"
-Xmx4G
この構成では、Heap以外のメモリが確保された瞬間に 確実にOOMKilledされます。
② Direct Memoryを制限していない
Netty / Kafka / Elasticsearch などは Direct Memoryを大量に使用します。
制限がない場合、以下のように無制限で拡張されます。
-XX:MaxDirectMemorySize=未設定(ほぼ無制限)
結果として Heap に余裕があっても Pod が即死します。
③ Java 8u191以前を使用している
古いJavaでは cgroupのメモリ制限を正しく認識しません。
- ホスト全体のメモリを基準に Heap を確保
- 結果として limits 超過
OOMKilled時の確認方法
Podの状態確認
kubectl describe pod <pod-name>
以下が表示されていれば cgroup OOM です。
State: Terminated
Reason: OOMKilled
Exit Code: 137
Java側のログの特徴
- java.lang.OutOfMemoryError が出ない
- Heap Dump が出ない
- 突然プロセスが終了する
これがKubernetes OOMKilledの最大の特徴です。
安全なメモリ設計の基本方針
① limits は Xmx の 1.3〜1.5倍
limits.memory = Xmx × 1.3〜1.5
例:
limits.memory: 6Gi
-Xmx4G
② 非Heapメモリを明示的に制御
-XX:MaxMetaspaceSize=256m
-XX:MaxDirectMemorySize=512m
-Xss512k
これにより OOMKilled の予測性が格段に向上します。
Kubernetes向け推奨設定例
resources:
requests:
memory: "4Gi"
limits:
memory: "6Gi"
JAVA_OPTS="
-Xms4G -Xmx4G
-XX:MaxMetaspaceSize=256m
-XX:MaxDirectMemorySize=512m
-Xss512k
"
OOMKilledを防ぐ運用ポイント
- limits = Xmx にしない
- Direct Memoryを必ず制限
- スレッド数を把握する
- Java 11以降を使用する
- 負荷試験で最大メモリ使用量を確認
まとめ
KubernetesでのOOMKilledは、 「Javaが悪い」のではなく「設計が合っていない」ことが原因です。
Heapだけを見るのではなく、 コンテナ全体で消費されるメモリを意識した設計が不可欠です。
Kubernetes × Java では、 OOMKilledは予測でき、防げる障害です。



