Add flags: production JVM flag cheat sheet for Spring Boot on JDK 25/27

MaxRAMPercentage vs a hardcoded -Xmx, -XX:-UseContainerSupport, the
ActiveProcessorCount lie, and which diagnostic flags are worth leaving
on in prod, verified in real Docker containers on JDK 25 and JDK 27.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-10-01 04:34:04 +00:00
co-authored by Claude Sonnet 5
parent e83c9949d8
commit 2f88734f5d
22 changed files with 832 additions and 0 deletions
@@ -0,0 +1,59 @@
$ for mem,cpu in 256m:1 512m:1 1800m:1 512m:2 1800m:2 4g:2; do
docker run --rm --memory=$mem --cpus=$cpu --entrypoint java flags-demo:jdk25 \
-XX:+PrintFlagsFinal -version | grep -E "UseG1GC|UseSerialGC|UseCompactObjectHeaders|MaxHeapSize|MaxRAMPercentage"
done
--- mem=256m cpus=1 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 132120576 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = false {product lp64_product} {default}
bool UseG1GC = false {product} {default}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = true {product} {ergonomic}
--- mem=512m cpus=1 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 134217728 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = false {product lp64_product} {default}
bool UseG1GC = false {product} {default}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = true {product} {ergonomic}
--- mem=1800m cpus=1 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 471859200 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = false {product lp64_product} {default}
bool UseG1GC = false {product} {default}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = true {product} {ergonomic}
--- mem=512m cpus=2 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 134217728 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = false {product lp64_product} {default}
bool UseG1GC = false {product} {default}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = true {product} {ergonomic}
--- mem=1800m cpus=2 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 471859200 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = false {product lp64_product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = false {product} {default}
--- mem=4g cpus=2 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 1073741824 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = false {product lp64_product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = false {product} {default}
@@ -0,0 +1,59 @@
$ for mem,cpu in 256m:1 512m:1 1800m:1 512m:2 1800m:2 4g:2; do
docker run --rm --memory=$mem --cpus=$cpu --entrypoint java flags-demo:jdk27 \
-XX:+PrintFlagsFinal -version | grep -E "UseG1GC|UseSerialGC|UseCompactObjectHeaders|MaxHeapSize|MaxRAMPercentage"
done
--- mem=256m cpus=1 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 132120576 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = true {product lp64_product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = false {product} {default}
--- mem=512m cpus=1 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 134217728 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = true {product lp64_product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = false {product} {default}
--- mem=1800m cpus=1 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 471859200 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = true {product lp64_product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = false {product} {default}
--- mem=512m cpus=2 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 134217728 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = true {product lp64_product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = false {product} {default}
--- mem=1800m cpus=2 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 471859200 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = true {product lp64_product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = false {product} {default}
--- mem=4g cpus=2 ---
int ActiveProcessorCount = -1 {product} {default}
size_t MaxHeapSize = 1073741824 {product} {ergonomic}
double MaxRAMPercentage = 25.000000 {product} {default}
bool UseCompactObjectHeaders = true {product lp64_product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = false {product} {default}
@@ -0,0 +1,13 @@
$ docker run --rm --cpus=1 --memory=1g --entrypoint java flags-demo:jdk27 -XX:+PrintFlagsFinal -version | grep -E "ParallelGCThreads|ConcGCThreads|ActiveProcessorCount"
$ docker run --rm --cpus=1 --memory=1g --entrypoint java flags-demo:jdk27 -XX:ActiveProcessorCount=8 -XX:+PrintFlagsFinal -version | grep -E "ParallelGCThreads|ConcGCThreads|ActiveProcessorCount"
=== ActiveProcessorCount lying to the JVM: 1 real CPU, told there are 8 ===
--- JDK27, --cpus=1, no override (correct) ---
int ActiveProcessorCount = -1 {product} {default}
uint ConcGCThreads = 1 {product} {ergonomic}
uint ParallelGCThreads = 1 {product} {default}
--- JDK27, --cpus=1, -XX:ActiveProcessorCount=8 (lying) ---
int ActiveProcessorCount = 8 {product} {command line}
uint ConcGCThreads = 2 {product} {ergonomic}
uint ParallelGCThreads = 8 {product} {default}
@@ -0,0 +1,27 @@
$ docker run -d --memory=1g --name hdr -v $PWD/docker/headersize.jar:/headersize.jar \
--entrypoint java flags-demo:<tag> [extra flags] -cp /headersize.jar HeaderSizeDemo 2000000
$ docker exec hdr jcmd 1 GC.class_histogram | grep -E "Pair|Total"
--- JDK 25, default (UseCompactObjectHeaders=false), flags-demo:jdk25-jdk ---
1: 2000000 48000000 HeaderSizeDemo$Pair
273: 1 24 java.lang.invoke.StringConcatFactory$InlineHiddenClassStrategy$MethodHandlePair ([email protected])
Total 2030279 57368512
--- JDK 25, -XX:+UseCompactObjectHeaders ---
1: 2000000 32000000 HeaderSizeDemo$Pair
300: 1 16 java.lang.invoke.StringConcatFactory$InlineHiddenClassStrategy$MethodHandlePair ([email protected])
Total 2030283 41272056
--- JDK 27, default (UseCompactObjectHeaders=true), flags-demo:jdk27 ---
1: 2000000 32000000 HeaderSizeDemo$Pair
317: 1 16 java.lang.invoke.StringConcatFactory$InlineHiddenClassStrategy$MethodHandlePair (java.base@27)
Total 2030937 41320144
--- JDK 27, -XX:-UseCompactObjectHeaders ---
1: 2000000 48000000 HeaderSizeDemo$Pair
282: 1 24 java.lang.invoke.StringConcatFactory$InlineHiddenClassStrategy$MethodHandlePair (java.base@27)
Total 2031092 57425040
2,000,000 instances of a 2-int object: 48,000,000 bytes (24 bytes each) with the old header,
32,000,000 bytes (16 bytes each) compact. 33% smaller per instance, reproduced symmetrically
in both directions on both JDKs.
@@ -0,0 +1,23 @@
$ docker run --rm --memory=256m --entrypoint java flags-demo:jdk25 \
-Xlog:gc -XX:MaxRAMPercentage=75 -cp /memoryhog.jar MemoryHog 24 200
[0.004s][info][gc] Using Serial
MemoryHog starting: chunk=24MB sleep=200ms
[0.048s][info][gc] GC(0) Pause Young (Allocation Failure) 1M->1M(7M) 0.713ms
held=24MB heapUsed=25MB heapMax=185MB freeHeap=6MB
[0.291s][info][gc] GC(1) Pause Young (Allocation Failure) 25M->25M(31M) 1.105ms
held=48MB heapUsed=49MB heapMax=185MB freeHeap=6MB
[0.507s][info][gc] GC(2) Pause Young (Allocation Failure) 49M->49M(77M) 0.643ms
held=72MB heapUsed=73MB heapMax=185MB freeHeap=27MB
[0.723s][info][gc] GC(3) Pause Young (Allocation Failure) 73M->73M(112M) 0.176ms
held=96MB heapUsed=98MB heapMax=185MB freeHeap=14MB
[0.962s][info][gc] GC(4) Pause Young (Allocation Failure) 98M->97M(147M) 23.535ms
held=120MB heapUsed=122MB heapMax=185MB freeHeap=24MB
[1.201s][info][gc] GC(5) Pause Young (Allocation Failure) 122M->121M(181M) 28.449ms
held=144MB heapUsed=146MB heapMax=185MB freeHeap=35MB
held=168MB heapUsed=170MB heapMax=185MB freeHeap=11MB
[1.633s][info][gc] GC(6) Pause Full (Allocation Failure) 170M->169M(185M) 5.895ms
[1.649s][info][gc] GC(7) Pause Full (Allocation Failure) 169M->169M(185M) 15.822ms
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at MemoryHog.main(MemoryHog.java:21)
exit=1
@@ -0,0 +1,26 @@
$ docker run --rm --memory=256m --entrypoint java flags-demo:jdk25 \
-Xlog:gc -Xmx1g -cp /memoryhog.jar MemoryHog 24 200
$ echo "exit code: $?"
$ docker inspect <id> --format "OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}"
[0.005s][info][gc] Using Serial
MemoryHog starting: chunk=24MB sleep=200ms
[0.054s][info][gc] GC(0) Pause Young (Allocation Failure) 1M->1M(7M) 0.685ms
held=24MB heapUsed=25MB heapMax=989MB freeHeap=6MB
[0.296s][info][gc] GC(1) Pause Young (Allocation Failure) 25M->25M(31M) 1.057ms
held=48MB heapUsed=49MB heapMax=989MB freeHeap=6MB
[0.513s][info][gc] GC(2) Pause Young (Allocation Failure) 49M->49M(77M) 0.606ms
held=72MB heapUsed=73MB heapMax=989MB freeHeap=27MB
[0.728s][info][gc] GC(3) Pause Young (Allocation Failure) 73M->73M(112M) 0.184ms
held=96MB heapUsed=98MB heapMax=989MB freeHeap=14MB
[0.959s][info][gc] GC(4) Pause Young (Allocation Failure) 98M->97M(147M) 15.593ms
held=120MB heapUsed=122MB heapMax=989MB freeHeap=24MB
[1.180s][info][gc] GC(5) Pause Young (Allocation Failure) 122M->121M(181M) 15.364ms
held=144MB heapUsed=146MB heapMax=989MB freeHeap=35MB
held=168MB heapUsed=170MB heapMax=989MB freeHeap=11MB
[1.634s][info][gc] GC(6) Pause Young (Allocation Failure) 170M->169M(251M) 34.003ms
held=192MB heapUsed=194MB heapMax=989MB freeHeap=56MB
held=216MB heapUsed=218MB heapMax=989MB freeHeap=32MB
container exit code: 137
error: no such object: hogB
@@ -0,0 +1,31 @@
$ docker run --rm --memory=256m --entrypoint java flags-demo:jdk25 \
-XX:-UseContainerSupport -XX:+PrintFlagsFinal -version | grep -E "MaxHeapSize|UseContainerSupport"
$ docker run --rm --memory=256m --entrypoint java flags-demo:jdk25 \
-Xlog:gc -XX:-UseContainerSupport -cp /memoryhog.jar MemoryHog 24 200
--- what the flag alone resolves heap to ---
size_t MaxHeapSize = 2105540608 {product} {ergonomic}
size_t SoftMaxHeapSize = 2105540608 {manageable} {ergonomic}
bool UseContainerSupport = false {product} {command line}
--- the hog run ---
[0.004s][info][gc] Using G1
MemoryHog starting: chunk=24MB sleep=200ms
held=24MB heapUsed=26MB heapMax=2008MB freeHeap=101MB
held=48MB heapUsed=52MB heapMax=2008MB freeHeap=75MB
[0.515s][info][gc] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 52M->51M(128M) 1.445ms
[0.515s][info][gc] GC(1) Concurrent Undo Cycle
[0.515s][info][gc] GC(1) Concurrent Undo Cycle 0.084ms
held=72MB heapUsed=76MB heapMax=2008MB freeHeap=51MB
[0.731s][info][gc] GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 76M->76M(128M) 1.569ms
[0.732s][info][gc] GC(3) Concurrent Mark Cycle
[0.748s][info][gc] GC(3) Pause Remark 101M->101M(172M) 0.580ms
[0.749s][info][gc] GC(3) Pause Cleanup 101M->101M(172M) 0.018ms
held=96MB heapUsed=101MB heapMax=2008MB freeHeap=70MB
[0.751s][info][gc] GC(3) Concurrent Mark Cycle 19.274ms
held=120MB heapUsed=126MB heapMax=2008MB freeHeap=45MB
held=144MB heapUsed=151MB heapMax=2008MB freeHeap=45MB
held=168MB heapUsed=176MB heapMax=2008MB freeHeap=45MB
held=192MB heapUsed=201MB heapMax=2008MB freeHeap=45MB
held=216MB heapUsed=226MB heapMax=2008MB freeHeap=45MB
container exit code: 137
@@ -0,0 +1,23 @@
$ docker run --rm --memory=256m -v $PWD/docker/memoryhog.jar:/memoryhog.jar --entrypoint java flags-demo:jdk25-jdk \
-XX:MaxRAMPercentage=75 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps/heap.hprof \
-cp /memoryhog.jar MemoryHog 24 200
=== Diagnostic: -XX:+HeapDumpOnOutOfMemoryError actually leaves a dump behind ===
MemoryHog starting: chunk=24MB sleep=200ms
held=24MB heapUsed=25MB heapMax=185MB freeHeap=6MB
held=48MB heapUsed=49MB heapMax=185MB freeHeap=6MB
held=72MB heapUsed=73MB heapMax=185MB freeHeap=27MB
held=96MB heapUsed=98MB heapMax=185MB freeHeap=14MB
held=120MB heapUsed=122MB heapMax=185MB freeHeap=24MB
held=144MB heapUsed=146MB heapMax=185MB freeHeap=35MB
held=168MB heapUsed=170MB heapMax=185MB freeHeap=11MB
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /dumps/heap.hprof ...
Heap dump file created [180390818 bytes in 1.028 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at MemoryHog.main(MemoryHog.java:21)
exit=1
total 176176
drwxr-xr-x 2 root root 4096 Oct 1 04:29 .
drwxr-xr-x 3 root root 4096 Oct 1 04:29 ..
-rw------- 1 root root 180390818 Oct 1 04:29 heap.hprof
@@ -0,0 +1,97 @@
$ docker run -d --memory=512m --name nmtdemo -v $PWD/docker/flags-demo.jar:/app.jar --entrypoint java flags-demo:jdk25-jdk \
-XX:NativeMemoryTracking=summary -XX:MaxRAMPercentage=75 -jar /app.jar
$ docker exec nmtdemo jcmd 1 VM.native_memory summary
=== Diagnostic: -XX:NativeMemoryTracking=summary via jcmd ===
1:
Native Memory Tracking:
(Omitting categories weighting less than 1KB)
Total: reserved=1824154KB, committed=115878KB
malloc: 34018KB #144561, peak=35841KB #140626
mmap: reserved=1790136KB, committed=81860KB
- Java Heap (reserved=393216KB, committed=23532KB)
(mmap: reserved=393216KB, committed=23532KB, at peak)
- Class (reserved=1049299KB, committed=5075KB)
(classes #8049)
( instance classes #7466, array classes #583)
(malloc=723KB tag=Class #15577) (at peak)
(mmap: reserved=1048576KB, committed=4352KB, at peak)
( Metadata: )
( reserved=65536KB, committed=27072KB)
( used=26779KB)
( waste=293KB =1.08%)
( Class space:)
( reserved=1048576KB, committed=4352KB)
( used=4187KB)
( waste=165KB =3.79%)
- Thread (reserved=17468KB, committed=1380KB)
(threads #17)
(stack: reserved=17408KB, committed=1320KB, peak=1320KB)
(malloc=42KB tag=Thread #105) (peak=51KB #108)
(arena=18KB #30) (peak=301KB #22)
- Code (reserved=252158KB, committed=16014KB)
(malloc=4465KB tag=Code #21734) (at peak)
(mmap: reserved=247692KB, committed=11548KB, at peak)
(arena=1KB #1) (peak=34KB #2)
- GC (reserved=1302KB, committed=102KB)
(malloc=22KB tag=GC #58) (peak=96KB #79)
(mmap: reserved=1280KB, committed=80KB, at peak)
- Compiler (reserved=8163KB, committed=8163KB)
(malloc=54KB tag=Compiler #86) (peak=69KB #91)
(arena=8110KB #14) (peak=22951KB #14)
- Internal (reserved=1324KB, committed=1324KB)
(malloc=1288KB tag=Internal #5521) (at peak)
(mmap: reserved=36KB, committed=36KB, at peak)
- Other (reserved=20KB, committed=20KB)
(malloc=20KB tag=Other #3) (at peak)
- Symbol (reserved=6745KB, committed=6745KB)
(malloc=6034KB tag=Symbol #93254) (at peak)
(arena=712KB #1) (at peak)
- Native Memory Tracking (reserved=2576KB, committed=2576KB)
(malloc=35KB tag=Native Memory Tracking #619) (at peak)
(tracking overhead=2541KB)
- Shared class space (reserved=16384KB, committed=13912KB, readonly=0KB)
(mmap: reserved=16384KB, committed=13912KB, peak=14120KB)
- Arena Chunk (reserved=9219KB, committed=9219KB)
(malloc=9219KB tag=Arena Chunk #541) (peak=23793KB #558)
- Tracing (reserved=5KB, committed=5KB)
(malloc=5KB tag=Tracing #22) (at peak)
- Module (reserved=80KB, committed=80KB)
(malloc=80KB tag=Module #2158) (at peak)
- Safepoint (reserved=8KB, committed=8KB)
(mmap: reserved=8KB, committed=8KB, at peak)
- Synchronization (reserved=483KB, committed=483KB)
(malloc=483KB tag=Synchronization #4738) (at peak)
- Serviceability (reserved=17KB, committed=17KB)
(malloc=17KB tag=Serviceability #14) (peak=20KB #18)
- Metaspace (reserved=65672KB, committed=27208KB)
(malloc=136KB tag=Metaspace #46) (at peak)
(mmap: reserved=65536KB, committed=27072KB, at peak)
- String Deduplication (reserved=1KB, committed=1KB)
(malloc=1KB tag=String Deduplication #8) (at peak)
- Object Monitors (reserved=13KB, committed=13KB)
(malloc=13KB tag=Object Monitors #69) (at peak)