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:
@@ -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]
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user