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
This commit is contained in:
2026-10-12 17:30:55 +00:00
co-authored by Claude Sonnet 5.5
parent fc05eed4b5
commit d565be93c6
13 changed files with 388 additions and 1 deletions
+70
View File
@@ -0,0 +1,70 @@
# 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/`](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`](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
```console
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`](output/03-summary.txt), raw blocks in [`output/02-runs.txt`](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`](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.