Files
javademos/jdk27-perf/README.md
T
asmhatreandClaude Sonnet 5.5 d565be93c6 Add jdk27-perf: the same Spring Boot service measured on JDK 25 and JDK 27
Live heap, throughput, p99 and startup across four configurations, including opt-in and opt-out of
compact object headers.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WzVQ6zFh9cuLPj9RSbRH8U
2026-10-12 17:30:55 +00:00

3.6 KiB

jdk27-perf — what actually got faster in JDK 27, measured on one Spring Boot service

Companion module for the ankurm.com post What Actually Got Faster in JDK 27: Measured on a Spring Boot App (https://ankurm.com/what-actually-got-faster-in-jdk-27-measured-spring-boot/). No docs/ folder: this README and the raw files in output/ are the documentation.

Question

JDK 27 changed two defaults that matter to a server: compact object headers are on (JEP 534) and G1 is the default collector everywhere (JEP 523). Does the same Spring Boot service get faster, smaller, or neither when you move from JDK 25 to JDK 27 with no flags?

Setup

Thing Value
JDK 25 Temurin 25.0.4.1+1 (LTS)
JDK 27 Temurin 27+35 (GA 2026-09-15)
Spring Boot 4.1.1, spring-boot-starter-webmvc
Machine 2 vCPU, 8 GB RAM Linux sandbox (the load client shares the box, so absolute numbers are modest and noisy)
Service 3,000,000 resident Item(int id, int qty) records; /quote?n=200 builds 200 response records; Jackson serialises them
Load closed loop, 4 client threads, 15 s warmup discarded, 30 s measured, client always JDK 25
Repetitions 3 per configuration, interleaved so machine drift hits all four equally

Four configurations: jdk25 defaults, jdk25 +CompactObjectHeaders (opt in), jdk27 defaults, jdk27 -CompactObjectHeaders (opt out). output/01-defaults.txt shows both JDKs pick G1 on this machine, so the collector is the same everywhere and only headers and the rest of the JDK differ.

Quick start

mvn -B -DskipTests package               # build with JDK 25 or later
export J25=/path/to/jdk-25 J27=/path/to/jdk-27
scripts/run-all.sh                       # about 12 minutes; regenerates output/
REPS=5 scripts/run-all.sh                # more repetitions

Results

Medians of 3 runs (output/03-summary.txt, raw blocks in output/02-runs.txt):

Config Startup ms Throughput req/s (range) p99 ms
jdk25 defaults 3437 6788 (6239-6868) 3.23
jdk25 +CompactObjectHeaders 3512 7157 (6407-7228) 3.09
jdk27 defaults 3220 7016 (6755-7275) 2.99
jdk27 -CompactObjectHeaders 3388 7010 (6867-7227) 3.03

Resident data set after a forced full GC (output/04-live-set.txt, identical in all three repetitions):

Config Live heap MiB
jdk25 defaults 93
jdk25 +CompactObjectHeaders 69
jdk27 defaults 70
jdk27 -CompactObjectHeaders 93-94

What it says

  • Footprint is the clear win. Compact headers cut the live heap by 23 to 24 MiB (whole-MiB rounding). That matches 3,000,000 objects times 8 bytes saved, which is 22.9 MiB. A two-int record goes from 24 to 16 bytes.
  • Speed is not distinguishable. The throughput ranges overlap in every row and p99 differs by fractions of a millisecond. On this box I cannot claim JDK 27 is faster.
  • Startup is inconclusive. JDK 27 defaults had the lowest median, but the individual runs overlap with JDK 25.
  • Resident set size and committed heap vary run to run with G1's sizing decisions; see the per-run /stats lines in 02-runs.txt before drawing a conclusion from them.

Honest notes

  • output/02-runs.txt was produced by the first build of the jar. The /live endpoint used for 04-live-set.txt was added afterwards; it does not touch the /quote path, but strictly the two files come from two builds of this module.
  • Timings drift. Treat them as indicative.

Licence

MIT, as the parent repository.