KubernetesでJavaがOOMKilledされる仕組み

  • URLをコピーしました!

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は予測でき、防げる障害です。

目次