1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

よくありそうなJava(Spring Boot)+外部PDF生成アプリのメモリトラブル調査・最適設定を探す

Last updated at Posted at 2026-01-03

Java のヒープは余っているのに、なぜかサーバがメモリ不足で落ちる。
原因は JVM の外側 ―― Native Memory と外部プロセスなんてことが良くありました。

本記事は、そんな同居構成で「事故らない」ための、私なりの調査と設定の整理です。

免責

※ 本記事はオンプレミス / Windows Server 環境を前提とした一例であり、システム構成・JDKバージョン・負荷特性により最適解は異なります。
※ 本記事は OpenJDK 11 / 17 系を想定しています。
※ 本構成では ZGC / Shenandoah は検証対象外としています。(Windows + 外部プロセス同居 + 運用実績重視のため)

背景と基本方針

Windows Server 上で Java(Spring Boot)アプリケーション
外部アプリケーション(例:帳票ツール) が同一サーバ上で稼働しており、
物理メモリ(実メモリ)を奪い合う構成 というようなよくありそうなシチュエーションをもとに、

メモリトラブル時の調査や最適設定を探すときの、私なりの手順をアウトプットしておきます。

このような構成では、Javaヒープ(Inside JVM)だけを見ていても不十分であり、
Native Memory(Outside JVM)を含めた実メモリ消費量の可視化と制御が不可欠だよなーっと捉えています。

そのため、

jcmd を用いて JVM の実メモリ使用状況(特に Native Memory)を定点観測し、
OS・外部プロセスを含めたメモリ配分を再設計する

というアプローチを採用することが良くあります。

※ 本記事は「同一サーバ上で JVM と外部プロセスが同居する構成」を前提としており、コンテナ環境や JVM 単独構成では最適解が異なる場合があります。

超参考情報

こちらは公式情報を見る前に、一読しておくべきと思いました。

こちらは公式情報をざっくり見た後に、目を通すことで何をどうしようか?という疑問を解決してくれると思いました。

ざっくり整理すると

JVM 全体の中での位置づけ

ざっくり階層で見ると・・・

JVM Native Memory
 ├─ Java Heap          ← アプリのオブジェクト
 ├─ Metaspace          ← クラス定義
 ├─ Code Cache         ← JITコード
 ├─ Thread             ← スタック
 ├─ Internal           ← JVM内部管理用
 └─ Symbol             ← 名前・識別子テーブル

ざっくり図にしてみると・・・


1. jcmd によるメモリ使用量の定期取得

Windows 環境において jcmd を定期実行するには、
PowerShell スクリプトを作成し、タスクスケジューラで定期実行する方式が楽ちんかなーって思っています。

Native Memory Tracking (NMT) の有効化

JVM 起動時に以下のオプションを指定し、再起動する。

-XX:NativeMemoryTracking=summary

※ 詳細調査が必要な短期間のみ detail を使用する感じですかね・・・
detail は CPU / メモリ負荷が高く、常時有効化は非推奨)

ログ取得スクリプト例(PowerShell)

複数 Java プロセスが存在する可能性を考慮し、単純な Get-Process java の使用は避けることが望ましい

$Date = Get-Date -Format "yyyyMMdd_HHmmss"

# jcmd の一覧から対象 JVM を特定(Spring Boot 等の識別子で絞り込み)
$Pid = (jcmd | Select-String "spring").ToString().Split()[0]

$LogFile = "C:\logs\jcmd_memory_$Date.log"

# Native Memory のサマリ出力
jcmd $Pid VM.native_memory summary > $LogFile

# クラスヒストグラム(クラス増加傾向確認用)
jcmd $Pid GC.class_histogram >> $LogFile

※ PID の誤取得を防ぐため、
可能であれば -Dspring.pid.file=xxx.pid による PID ファイル出力を併用する。


2. 分析のポイント:どこを見るべきか

jcmd VM.native_memory summary の結果から、以下を重点的に確認する。

Reserved vs Committed

  • Committed(実使用量)を主指標とする
  • Windows では Reserved が大きくても即問題とはならない
  • Committed の合計が物理メモリ+ページファイルの上限に近づいていないかを確認する

用語メモ

  • Reserved:「席だけ確保した(使うかもしれないと言って場所を押さえた)」

  • Committed:「実際に人が座っている(本当に使っている)」

障害になるのはほぼ常に Committed かなーって感じています。

Internal / Symbol

  • 異常な増加がある場合、以下を疑う

    • クラスローダリーク
    • リフレクション・動的プロキシの多用
    • Spring Boot Fat Jar 特有のクラスロード挙動
    • PDFライブラリのクラス解放漏れ

※ 一定水準で頭打ちになっている場合は問題にならないことも多く、「増え続けているかどうか」を時系列で確認することが重要

用語メモ

  • Internal:JVM の内部実装が使う Native メモリ
  • Symbol: クラス名・メソッド名・フィールド名などの「名前情報(Symbol Table)」

Thread

  • 1 スレッドあたり 約 1MB の Native Stack を消費
  • Tomcat / Spring Boot のスレッド上限設定と突き合わせる
  • スレッド数増加=ヒープ外メモリ増加、という認識を持つ

※ デフォルトでは約1MBだが、-Xss により変更可能

3. GC 設定(CATALINA_OPTS 等)の妥当性確認

最大ヒープサイズ (-Xmx) の見直し

「ヒープを潤沢に割り当てている」構成は、
外部PDF生成プロセスを含む Windows 環境では逆効果になるケースが多い

例:

  • 物理メモリ:32GB
  • Java に 28GB 割り当て
    → OS・Native・外部プロセス領域が枯渇しやすい

考え方

Java Xmx = 実メモリ −(OS + PDF生成ピーク + JVM Native 余白)

G1GC の利用

  • 同居構成・安定性重視の場合は G1GC を第一候補とする
  • メモリ断片化耐性、返却挙動の安定性を重視
-XX:+UseG1GC

Metaspace 上限の設定

Metaspace は Native 領域を使用するため、
上限未設定の場合、Native Memory を際限なく消費するリスクがある

-XX:MaxMetaspaceSize=512m ~ 1g

※ 小さくしすぎると起動失敗の可能性があるため注意

4. 解決に向けたアプローチ手順

① 現状把握(約 1 週間)

  • NMT(summary)有効化
  • jcmd による定期ログ取得
  • 障害発生直前の Committed 合計値を把握

② 外部プロセス(PDF生成)の負荷測定

  • Windows パフォーマンスモニターを使用

    • 対象 PDF プロセスの Working Set / Commit
  • 同時起動数とピークメモリ使用量を必ず確認

③ メモリ配分の再設計

  • Java Heap:必要最小限まで削減
  • JVM Native + OS + PDF生成用の余白を確保
  • 同時 PDF 生成数が多い場合は制御(キュー化)を検討

④ 設定反映と再検証

  • CATALINA_OPTS 反映
  • 再度 jcmd / PerfMon を用いて挙動を確認
  • Committed が安定して頭打ちになるかを評価

注意点(Windows Server 特有?)

  • タスクマネージャーの「利用可能」メモリは信用しない
  • **「コミット済み(Committed Bytes)」と「Commit Limit」**を必ず確認
  • ページファイルが小さい構成は即座にメモリ枯渇を招く

まとめ

まずは、

Native Memory Tracking (summary) を有効化し、jcmd による数日〜1週間のログ収集を行う

ことから着手する。

これにより、

  • Java が原因なのか
  • 外部 PDF プロセスが主因なのか
  • OS のメモリ設計が破綻しているのか

定量的に切り分けることが可能となるかなーと思います。

最後に

Java のメモリトラブルは、「ヒープを見て終わり」では解決しないケースが存在すると思っています。

特に JVM と外部プロセスが同居する構成では、Native Memory・OS・GC の振る舞い を1つのシステムとして捉える視点が重要だと感じています。

同じような構成で悩んでいる方の、調査・設計のヒントになれば幸いです。


以下は、よく忘れがちなので手書きノートから転記しておきます。

GC 実行時オプション設定~判断材料のエッセンス整理~

前提条件(この判断が必要な環境)

  • Windows Server
  • Java(Spring Boot)+ 外部 PDF 生成プロセスが同居
  • CPU / メモリを JVM が独占してはいけない

1. なぜ GC の実行時オプションを「明示指定」するのか

JVM デフォルトの問題点

  • JVM は CPU コア数 ≒ GC スレッド数 で自動設定する

  • その結果:

    • GC 実行時に CPU を一気に使い切る
    • 外部 PDF プロセスが詰まる
    • Java / PDF 両方が不安定になる

デフォルト任せは「単独 JVM 前提」
同居構成では危険

2. 最低限、制御すべき GC パラメータ

必須で見るのはこの 2 つかなーー

-XX:ParallelGCThreads
-XX:ConcGCThreads
パラメータ 意味 目的
ParallelGCThreads STW GC の並列数 CPU の瞬間独占を防ぐ
ConcGCThreads 並行 GC の並列数 平常時の CPU 食いを防ぐ

「GC に CPU を全部渡さない」ための制御

3. 採用すべき GC 戦略の判断

基本方針

  • 同居構成・安定性重視の場合は G1GC を第一候補とする
  • Parallel GC は第二候補

理由:

  • 停止時間が分散される
  • 外部プロセス同居に向く
  • チューニング余地がある

4. GC スレッド数の決め方(判断ルール)

原則ルール(暗記用)

GC スレッド数は「CPU の 1/3〜1/4 程度」

具体例

vCPU ParallelGCThreads ConcGCThreads
8 2〜3 1
16 4〜5 2
32 6〜8 3

CPU 全部を GC に使わせないことが最重要

5. GC 停止時間に対する考え方

-XX:MaxGCPauseMillis

  • 「目標値」であって保証ではない
  • 値を小さくしすぎると GC が暴れる

実務目安

500ms 前後

安定性優先、低遅延は二の次

6. 実行時オプション決定の判断フロー(超重要)

私的にはこんな判断をしている。

7. エッセンスだけを抜いた最小構成例

-XX:+UseG1GC
-XX:ParallelGCThreads=4
-XX:ConcGCThreads=2
-XX:MaxGCPauseMillis=500

これだけで「事故りにくさ」が段違い というイメージです。

8. 判断の軸(判断責任者へのレビュー用ひとこと)

「GC の速さより、サーバ全体の安定性を優先する構成」が私としては好みですねーっと言ってみる。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?