systemd MemoryMaxとJavaメモリ設計の落とし穴

  • URLをコピーしました!

Linux環境でJavaアプリをsystemd管理している場合、 MemoryMaxの設定が原因で予期せぬOOMやプロセス強制終了が発生するケースが非常に多くあります。 本記事では、systemdのMemoryMaxとJavaメモリ(Heap / Metaspace / Direct Memory)の関係を整理し、 なぜ「Heapに余裕があるのに落ちる」のかを実務視点で解説します。

目次

この記事でわかること

systemdのMemoryMaxが何を制御しているのか、Javaのどのメモリ領域が制限対象になるのかを理解できます。 また、実際に起きがちな障害パターンと、安全な設計・設定例まで把握できます。

systemd MemoryMaxとは何か

MemoryMax は systemd の unit ファイルで設定できる cgroup単位のメモリ上限です。 この値を超えた場合、カーネルはプロセスを OOM Kill します。

[Service]
MemoryMax=4G

重要なポイントは、MemoryMaxが制限するのは 「JVMヒープだけではない」という点です。

MemoryMaxが制限するメモリ範囲

MemoryMaxは以下をすべて合算して制限します。

  • Java Heap(-Xmx)
  • Metaspace(クラス情報)
  • Direct Memory(ByteBuffer等)
  • Thread Stack(スレッド数 × スタックサイズ)
  • JNI / ネイティブライブラリ使用分

つまり、-XmxをMemoryMax未満にしても安全とは限りません

よくある誤解①:XmxをMemoryMax以下にしている

以下のような設定は一見安全そうに見えます。

MemoryMax=4G
-Xmx3G

しかし実際の使用メモリは次のようになります。

  • Heap:3GB
  • Metaspace:200〜500MB
  • Direct Memory:〜1GB(NIO使用時)
  • Thread Stack:数百MB

結果としてMemoryMaxを超過し、cgroup OOMが発生します。

よくある誤解②:JavaのOOMログが出ない

MemoryMaxによるOOMはJVM外で発生するため、 以下の特徴があります。

  • java.lang.OutOfMemoryError が出ない
  • hs_err_pid*.log が出ない
  • プロセスが突然 SIGKILL される

このため「原因不明の再起動」と誤認されがちです。

確認すべきログとコマンド

systemdステータス

systemctl status your-service

以下のような表示があれば MemoryMax が原因です。

code=killed, signal=KILL
Memory cgroup out of memory

カーネルログ

dmesg | grep -i memory

Javaメモリ設計の正しい考え方

MemoryMax を設定する場合、Heap以外を必ず考慮します。

安全な目安

MemoryMax = Xmx × 1.3〜1.5

例:

-Xmx4G
MemoryMax=6G

Direct Memoryの罠

Netty / Kafka / Elasticsearch などを使う場合、 Direct Memory が非常に大きくなります。

制御するには以下を明示します。

-XX:MaxDirectMemorySize=512m

これを設定しないと、MemoryMax超過の最大要因になります。

Metaspaceの管理

クラス動的ロードが多いアプリでは Metaspace が肥大化します。

-XX:MaxMetaspaceSize=256m

これにより予期せぬcgroup OOMを防げます。

推奨設定例(systemd + Java)

[Service]
MemoryMax=6G
ExecStart=/usr/bin/java \
  -Xms4G -Xmx4G \
  -XX:MaxMetaspaceSize=256m \
  -XX:MaxDirectMemorySize=512m \
  -jar app.jar

MemoryMaxを使うべきケース・使うべきでないケース

使うべき

  • 複数サービス同居サーバー
  • メモリ暴走防止が目的

使うべきでない

  • 単一Javaサービス専用サーバー
  • メモリを最大限使いたいバッチ処理

まとめ

systemd MemoryMax は強力だが非常に危険な制限です。 Javaのメモリ構造を理解せずに設定すると、 Heapに余裕があるのに突然死ぬという最悪の障害を引き起こします。

必ず「Heap + 非Heap + OS使用分」を意識した設計を行いましょう。

目次