Add jdk27-memory: compact object headers and G1-everywhere on a Spring Boot service

Spring Boot 4.1.1 catalog service measured on JDK 25, 26 and 27: live heap 320 MB to 273 MB with compact headers, per-class bytes per instance, container-sizing sweeps with real cgroup limits, 1-CPU Serial vs G1 with Native Memory Tracking, removed flags (MaxRAM, UseCompressedClassPointers), and a kernel OOM-kill check. Outputs in jdk27-memory/output.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TF9JWFvJSNm6HVzswzZU5a
This commit is contained in:
Claude
2026-09-24 15:09:55 +00:00
parent e189da79ba
commit e83c9949d8
29 changed files with 1041 additions and 0 deletions
@@ -0,0 +1,41 @@
$ curl -s -w ' [HTTP %{http_code}]\n' localhost:18081/memory
java.version=27
UseCompactObjectHeaders=true
collectors=[G1 Young Generation, G1 Concurrent GC, G1 Old Generation]
availableProcessors=2
maxHeapMB=1024
products=0
heapLiveAfterGcMB=12
heapCommittedMB=1024
rssMB=238
gcCountTotal=7
gcMillisTotal=121
[HTTP 200]
$ curl -s -w ' [HTTP %{http_code}]\n' localhost:18081/load?n=2000000
loaded=2000000
loadMillis=237
gcCountDuringLoad=0
gcMillisDuringLoad=0
[HTTP 200]
$ curl -s -w ' [HTTP %{http_code}]\n' localhost:18081/memory
java.version=27
UseCompactObjectHeaders=true
collectors=[G1 Young Generation, G1 Concurrent GC, G1 Old Generation]
availableProcessors=2
maxHeapMB=1024
products=2000000
heapLiveAfterGcMB=272
heapCommittedMB=1024
rssMB=417
gcCountTotal=9
gcMillisTotal=626
[HTTP 200]
$ curl -s -w ' [HTTP %{http_code}]\n' localhost:18081/products/42
{"id":42,"priceCents":142,"stock":42,"active":true,"sku":"SKU-100000042"} [HTTP 200]
$ curl -s -w ' [HTTP %{http_code}]\n' localhost:18081/products/2000000
{"timestamp":"2026-09-24T14:57:54.671Z","status":404,"error":"Not Found","path":"/products/2000000"} [HTTP 404]
+44
View File
@@ -0,0 +1,44 @@
== JDK 25: OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)
default (this machine: 2 CPUs) -> compact=false collector=UseG1GC MaxHeapSize=1498MB
-XX:+UseCompactObjectHeaders -> compact=true collector=UseG1GC MaxHeapSize=1498MB
-XX:-UseCompactObjectHeaders -> compact=false collector=UseG1GC MaxHeapSize=1498MB
== JDK 26: OpenJDK Runtime Environment Temurin-26.0.2.1+1 (build 26.0.2.1+1)
default (this machine: 2 CPUs) -> compact=false collector=UseG1GC MaxHeapSize=1498MB
-XX:+UseCompactObjectHeaders -> compact=true collector=UseG1GC MaxHeapSize=1498MB
-XX:-UseCompactObjectHeaders -> compact=false collector=UseG1GC MaxHeapSize=1498MB
== JDK 27: OpenJDK Runtime Environment Temurin-27+35 (build 27+35)
default (this machine: 2 CPUs) -> compact=true collector=UseG1GC MaxHeapSize=1498MB
-XX:+UseCompactObjectHeaders -> compact=true collector=UseG1GC MaxHeapSize=1498MB
-XX:-UseCompactObjectHeaders -> compact=false collector=UseG1GC MaxHeapSize=1498MB
== The same JDKs in a container: 1 GB memory limit, 1 CPU (a cgroup, set the way Docker sets it)
JDK 25, 1 GB, 1 CPU -> compact=false collector=UseSerialGC MaxHeapSize=256MB
JDK 25, 1 GB, 1 CPU, MaxRAMPercentage=75 -> compact=false collector=UseSerialGC MaxHeapSize=768MB
JDK 26, 1 GB, 1 CPU -> compact=false collector=UseSerialGC MaxHeapSize=256MB
JDK 26, 1 GB, 1 CPU, MaxRAMPercentage=75 -> compact=false collector=UseSerialGC MaxHeapSize=768MB
JDK 27, 1 GB, 1 CPU -> compact=true collector=UseG1GC MaxHeapSize=256MB
JDK 27, 1 GB, 1 CPU, MaxRAMPercentage=75 -> compact=true collector=UseG1GC MaxHeapSize=768MB
== 1 GB, 2 CPUs
JDK 26, 1 GB, 2 CPUs -> compact=false collector=UseSerialGC MaxHeapSize=256MB
JDK 27, 1 GB, 2 CPUs -> compact=true collector=UseG1GC MaxHeapSize=256MB
== -XX:MaxRAM: the flag many people used to fake a container limit
JDK 26 -XX:MaxRAM=1g -> compact=false collector=UseG1GC MaxHeapSize=256MB
JVM says: OpenJDK 64-Bit Server VM warning: Option MaxRAM was deprecated in version 26.0 and will likely be removed in a future release.
JDK 27 -XX:MaxRAM=1g -> compact=true collector=UseG1GC MaxHeapSize=1498MB
JVM says: OpenJDK 64-Bit Server VM warning: Ignoring option MaxRAM; support was removed in 27.0
== Compact headers with each collector on JDK 27
JDK 27 -XX:+UseG1GC -> compact=true collector=UseG1GC MaxHeapSize=1498MB
JDK 27 -XX:+UseParallelGC -> compact=true collector=UseParallelGC MaxHeapSize=1498MB
JDK 27 -XX:+UseSerialGC -> compact=true collector=UseSerialGC MaxHeapSize=1498MB
JDK 27 -XX:+UseZGC -> compact=true collector=UseZGC MaxHeapSize=1498MB
== Compressed pointers on JDK 27
JDK 27 -XX:-UseCompressedClassPointers -> compact=true collector=UseG1GC MaxHeapSize=1498MB
JVM says: OpenJDK 64-Bit Server VM warning: Ignoring option UseCompressedClassPointers; support was removed in 27.0
JDK 27 -XX:-UseCompressedOops -> compact=true collector=UseG1GC MaxHeapSize=1498MB
JDK 27 -Xmx32g (a large explicit heap) -> compact=true collector=UseG1GC MaxHeapSize=32768MB
+52
View File
@@ -0,0 +1,52 @@
# 2026-09-24 2000000 products, -Xms1g -Xmx1g, G1, 5 runs per configuration; MED lines are medians
# JDK25=OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)
# JDK26=OpenJDK Runtime Environment Temurin-26.0.2.1+1 (build 26.0.2.1+1)
# JDK27=OpenJDK Runtime Environment Temurin-27+35 (build 27+35)
run jdk25-default/1 idle=13MB live=321MB rss=462MB gcDuringLoad=0 (0ms) lookup=246ns compact=false
run jdk25-default/2 idle=12MB live=320MB rss=461MB gcDuringLoad=0 (0ms) lookup=310ns compact=false
run jdk25-default/3 idle=13MB live=320MB rss=696MB gcDuringLoad=1 (213ms) lookup=187ns compact=false
run jdk25-default/4 idle=12MB live=321MB rss=462MB gcDuringLoad=0 (0ms) lookup=244ns compact=false
run jdk25-default/5 idle=12MB live=320MB rss=459MB gcDuringLoad=0 (0ms) lookup=282ns compact=false
MED jdk25-default idle=12MB live=320MB rss=462MB gcDuringLoad=0 gcMs=0 lookupNs=246
run jdk25-compact/1 idle=12MB live=273MB rss=416MB gcDuringLoad=0 (0ms) lookup=256ns compact=true
run jdk25-compact/2 idle=11MB live=273MB rss=415MB gcDuringLoad=0 (0ms) lookup=245ns compact=true
run jdk25-compact/3 idle=11MB live=273MB rss=415MB gcDuringLoad=0 (0ms) lookup=265ns compact=true
run jdk25-compact/4 idle=11MB live=273MB rss=667MB gcDuringLoad=1 (236ms) lookup=158ns compact=true
run jdk25-compact/5 idle=11MB live=272MB rss=413MB gcDuringLoad=0 (0ms) lookup=256ns compact=true
MED jdk25-compact idle=11MB live=273MB rss=415MB gcDuringLoad=0 gcMs=0 lookupNs=256
run jdk26-default/1 idle=13MB live=320MB rss=665MB gcDuringLoad=1 (203ms) lookup=202ns compact=false
run jdk26-default/2 idle=13MB live=320MB rss=693MB gcDuringLoad=1 (216ms) lookup=199ns compact=false
run jdk26-default/3 idle=13MB live=320MB rss=701MB gcDuringLoad=1 (206ms) lookup=181ns compact=false
run jdk26-default/4 idle=13MB live=320MB rss=647MB gcDuringLoad=1 (208ms) lookup=210ns compact=false
run jdk26-default/5 idle=13MB live=320MB rss=697MB gcDuringLoad=1 (198ms) lookup=188ns compact=false
MED jdk26-default idle=13MB live=320MB rss=693MB gcDuringLoad=1 gcMs=206 lookupNs=199
run jdk26-compact/1 idle=12MB live=274MB rss=419MB gcDuringLoad=0 (0ms) lookup=250ns compact=true
run jdk26-compact/2 idle=11MB live=273MB rss=645MB gcDuringLoad=1 (209ms) lookup=176ns compact=true
run jdk26-compact/3 idle=11MB live=273MB rss=416MB gcDuringLoad=0 (0ms) lookup=271ns compact=true
run jdk26-compact/4 idle=11MB live=273MB rss=418MB gcDuringLoad=0 (0ms) lookup=256ns compact=true
run jdk26-compact/5 idle=11MB live=273MB rss=615MB gcDuringLoad=1 (207ms) lookup=237ns compact=true
MED jdk26-compact idle=11MB live=273MB rss=419MB gcDuringLoad=0 gcMs=0 lookupNs=250
run jdk27-default/1 idle=12MB live=273MB rss=413MB gcDuringLoad=0 (0ms) lookup=298ns compact=true
run jdk27-default/2 idle=12MB live=272MB rss=412MB gcDuringLoad=0 (0ms) lookup=223ns compact=true
run jdk27-default/3 idle=12MB live=273MB rss=413MB gcDuringLoad=0 (0ms) lookup=318ns compact=true
run jdk27-default/4 idle=11MB live=273MB rss=594MB gcDuringLoad=1 (202ms) lookup=194ns compact=true
run jdk27-default/5 idle=12MB live=273MB rss=409MB gcDuringLoad=0 (0ms) lookup=257ns compact=true
MED jdk27-default idle=12MB live=273MB rss=413MB gcDuringLoad=0 gcMs=0 lookupNs=257
run jdk27-optout/1 idle=13MB live=319MB rss=454MB gcDuringLoad=0 (0ms) lookup=267ns compact=false
run jdk27-optout/2 idle=13MB live=319MB rss=461MB gcDuringLoad=0 (0ms) lookup=261ns compact=false
run jdk27-optout/3 idle=13MB live=320MB rss=459MB gcDuringLoad=0 (0ms) lookup=256ns compact=false
run jdk27-optout/4 idle=13MB live=320MB rss=616MB gcDuringLoad=1 (191ms) lookup=184ns compact=false
run jdk27-optout/5 idle=13MB live=320MB rss=454MB gcDuringLoad=0 (0ms) lookup=260ns compact=false
MED jdk27-optout idle=13MB live=320MB rss=459MB gcDuringLoad=0 gcMs=0 lookupNs=260
== medians of 5 runs per configuration (idle = live set of the idle app; live = after loading the catalog)
MED jdk25-default idle=12MB live=320MB rss=462MB gcDuringLoad=0 gcMs=0 lookupNs=246
MED jdk25-compact idle=11MB live=273MB rss=415MB gcDuringLoad=0 gcMs=0 lookupNs=256
MED jdk26-default idle=13MB live=320MB rss=693MB gcDuringLoad=1 gcMs=206 lookupNs=199
MED jdk26-compact idle=11MB live=273MB rss=419MB gcDuringLoad=0 gcMs=0 lookupNs=250
MED jdk27-default idle=12MB live=273MB rss=413MB gcDuringLoad=0 gcMs=0 lookupNs=257
MED jdk27-optout idle=13MB live=320MB rss=459MB gcDuringLoad=0 gcMs=0 lookupNs=260
== the same runs, grouped by whether a collection ran while the catalog was loading (all configurations together)
no collection during load runs=19 rss MB: min 409 / median 418 / max 462 lookup ns: min 223 / median 257 / max 318
a collection during load runs=11 rss MB: min 594 / median 665 / max 701 lookup ns: min 158 / median 188 / max 237
+12
View File
@@ -0,0 +1,12 @@
== bytes per instance (= #bytes / #instances) from jcmd GC.class_histogram after loading 2,000,000 products
class JDK 26 default JDK 27 default JDK 27 opt-out
com.ankurm.memory.Product 40.0 32.0 40.0
java.util.HashMap$Node 32.0 24.0 32.0
java.lang.Long 24.0 16.0 24.0
java.lang.String 24.0 24.0 24.0
[B 32.7 32.6 32.7
== total bytes of those five classes
jdk26-default 309088088 bytes (294.8 MB)
jdk27-default 260853264 bytes (248.8 MB)
jdk27-optout 309272896 bytes (294.9 MB)
+40
View File
@@ -0,0 +1,40 @@
== JDK 25
java 25.0.4.1 compact headers: false
id offset 16
priceCents offset 12
stock offset 24
active offset 28
sku offset 32
first field starts at byte 12 (so the header is at most 12 bytes)
a hard-coded 12-byte header would be right here
== JDK 26
java 26.0.2.1 compact headers: false
id offset 16
priceCents offset 12
stock offset 24
active offset 28
sku offset 32
first field starts at byte 12 (so the header is at most 12 bytes)
a hard-coded 12-byte header would be right here
== JDK 27
java 27 compact headers: true
id offset 8
priceCents offset 16
stock offset 20
active offset 24
sku offset 28
first field starts at byte 8 (so the header is at most 8 bytes)
a hard-coded 12-byte header would be WRONG here
== JDK 27 -XX:-UseCompactObjectHeaders
java 27 compact headers: false
id offset 16
priceCents offset 12
stock offset 24
active offset 28
sku offset 32
first field starts at byte 12 (so the header is at most 12 bytes)
a hard-coded 12-byte header would be right here
@@ -0,0 +1,20 @@
# 2000000 products; 2 CPUs; 3 runs per cell; rss is the median of the runs that finished
== Heap set to 75% of the container (-XX:MaxRAMPercentage=75)
limit heap JDK 26 default JDK 27 default JDK 27 opt-out
400MB 300MB 0/3 ok [1x java-OOM 2x no-result-in-25s] 3/3 ok rss 408MB 0/3 ok [3x killed]
416MB 312MB 0/3 ok [3x java-OOM] 3/3 ok rss 415MB 0/3 ok [3x java-OOM]
432MB 324MB 0/3 ok [3x java-OOM] 3/3 ok rss 406MB 3/3 ok rss 450MB
448MB 336MB 3/3 ok rss 454MB 3/3 ok rss 418MB 3/3 ok rss 458MB
512MB 384MB 3/3 ok rss 468MB 3/3 ok rss 417MB 3/3 ok rss 464MB
704MB 528MB 3/3 ok rss 527MB 3/3 ok rss 431MB 3/3 ok rss 465MB
== Heap left at the JVM default (25% of the container)
limit heap JDK 26 default JDK 27 default JDK 27 opt-out
1024MB 256MB 0/3 ok [3x java-OOM] 0/3 ok [3x java-OOM] 0/3 ok [3x java-OOM]
1152MB 288MB 0/3 ok [3x java-OOM] 3/3 ok rss 410MB 0/3 ok [3x java-OOM]
1216MB 304MB 0/3 ok [3x java-OOM] 3/3 ok rss 409MB 0/3 ok [3x java-OOM]
1280MB 320MB 0/3 ok [3x java-OOM] 3/3 ok rss 408MB 3/3 ok rss 444MB
1536MB 384MB 3/3 ok rss 465MB 3/3 ok rss 417MB 3/3 ok rss 463MB
2048MB 512MB 3/3 ok rss 485MB 3/3 ok rss 435MB 3/3 ok rss 486MB
# runs that needed a startup retry (the JVM never came up, for a reason other than OOM or a kernel kill): 0
+26
View File
@@ -0,0 +1,26 @@
# 2000000 products, container = 1 GB / 1 CPU (cgroup), -XX:MaxRAMPercentage=50 (512 MB heap), NMT summary on, medians of 3 runs
== jdk26-default (the JVM chose)
collectors=[Copy, MarkSweepCompact] cpus=1
startup=4.808s live=320MB rss=529MB loadMillis=695 gcDuringLoad=9 (523ms)
NMT: GC committed=1730 KB Total committed=597325 KB
== jdk26 -XX:+UseG1GC
collectors=[G1 Young Generation, G1 Concurrent GC, G1 Old Generation] cpus=1
startup=4.8s live=320MB rss=486MB loadMillis=909 gcDuringLoad=12 (517ms)
NMT: GC committed=46627 KB Total committed=642767 KB
== jdk27-default (the JVM chose)
collectors=[G1 Young Generation, G1 Concurrent GC, G1 Old Generation] cpus=1
startup=4.8s live=272MB rss=436MB loadMillis=700 gcDuringLoad=8 (472ms)
NMT: GC committed=43443 KB Total committed=493486 KB
== jdk27 -XX:+UseSerialGC
collectors=[Copy, MarkSweepCompact] cpus=1
startup=5.101s live=271MB rss=482MB loadMillis=692 gcDuringLoad=9 (495ms)
NMT: GC committed=1730 KB Total committed=597439 KB
== jdk27 -XX:+UseSerialGC, compact off
collectors=[Copy, MarkSweepCompact] cpus=1
startup=4.706s live=318MB rss=530MB loadMillis=630 gcDuringLoad=9 (487ms)
NMT: GC committed=1730 KB Total committed=597465 KB
+17
View File
@@ -0,0 +1,17 @@
# JDK 27, -XX:-UseCompactObjectHeaders -XX:MaxRAMPercentage=75, container limit 400 MB, 2 CPUs; 2,000,000 products
== attempt 1: /load answered HTTP 000; process gone, exit 137
memory.failcnt=155 memory.max_usage=400MB limit=400MB oom_kill 1
== attempt 2: /load answered HTTP 500; process still alive
memory.failcnt=0 memory.max_usage=397MB limit=400MB oom_kill 0
== attempt 3: /load answered HTTP 000; process gone, exit 137
memory.failcnt=182 memory.max_usage=400MB limit=400MB oom_kill 1
== attempt 4: /load answered HTTP 000; process gone, exit 137
memory.failcnt=101 memory.max_usage=400MB limit=400MB oom_kill 1
== the kernel log (dmesg): what it says about the processes that disappeared above
Memory cgroup out of memory: Killed process 21173 (java) total-vm:2923028kB, anon-rss:407580kB, file-rss:27492kB, shmem-rss:0kB, UID:0 pgtables:1128kB oom_score_adj:0
Memory cgroup out of memory: Killed process 21325 (java) total-vm:2920972kB, anon-rss:407492kB, file-rss:27304kB, shmem-rss:0kB, UID:0 pgtables:1128kB oom_score_adj:0
Memory cgroup out of memory: Killed process 21400 (java) total-vm:2920972kB, anon-rss:407424kB, file-rss:27080kB, shmem-rss:0kB, UID:0 pgtables:1128kB oom_score_adj:0