Spring Boot 4.1.1 harness measuring first-response time versus initial heap size and a 20,000-request warmup. Shows -XX:+ZAdaptiveHeapSizing is not yet in any build. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WzVQ6zFh9cuLPj9RSbRH8U
3.7 KiB
jdk28-zgc — what ZGC startup and heap sizing look like today, and what JEP 545 / 546 propose
Companion module for the ankurm.com post
ZGC in JDK 28: Adaptive Heap Sizing (JEP 546) and Faster Startup (JEP 545).
There is no docs/ folder: this README and the captured files in output/ are the documentation.
The honest status
On 12 October 2026 both JEPs show "Proposed to Target" for release 28 on openjdk.org. Neither is integrated.
output/01-flag-probe.txt proves it: -XX:+ZAdaptiveHeapSizing is an
Unrecognized VM option on JDK 25.0.4.1, Temurin 27+35 and OpenJDK 28-ea+19.
So this module does not benchmark the proposed features. It measures the baseline they are meant to improve,
on three JDKs, so you can rerun it the day a build contains the flag.
Versions
| Component | Version |
|---|---|
| JDK 25 | Temurin 25.0.4.1+1 (LTS) |
| JDK 27 | Temurin 27+35 (GA 2026-09-15) |
| JDK 28 | OpenJDK 28-ea+19 (jdk.java.net build 19, 2026-10-08) |
| Spring Boot | 4.1.1 (spring-boot-starter-webmvc) |
| Machine | 2 vCPU, 8 GB RAM Linux sandbox (a small box: absolute numbers are illustrative) |
Quick start
mvn -B -DskipTests package # build with any JDK 25+
export J25=/path/to/jdk-25 J27=/path/to/jdk-27 J28=/path/to/jdk-28-ea
scripts/run-all.sh # regenerates everything in output/
RUNS=10 scripts/run-all.sh # more runs, tighter medians
scripts/startup.sh $J28 "my label" 5 -XX:+UseZGC -Xms1g
Files
| File | What it is |
|---|---|
src/main/java/com/ankurm/zgc/DemoApplication.java |
One server: /hello allocates 16 KB, /heap reports committed and max heap |
scripts/startup.sh |
Wall-clock ms from process start to first HTTP 200, repeated, with min/median/max |
scripts/warmup.sh + scripts/Warmup.java |
20,000 requests, 4 threads, timing each batch of 2,000; client is always JDK 25 |
scripts/run-all.sh |
Runs all three experiments |
Results (median of 5 runs, milliseconds to first response)
| Config | JDK 25 | JDK 27 | JDK 28-ea |
|---|---|---|---|
| ZGC defaults | 3439 | 3421 | 3420 |
-Xms64m |
3299 | 3276 | 3350 |
-Xms1g |
3478 | 3275 | 3642 |
-Xms3g |
3836 | 3618 | 3803 |
-Xms3g -XX:+AlwaysPreTouch |
4834 | 4560 | 5010 |
Raw per-run lines: output/02-startup-vs-xms.txt.
Warmup, total ms for the first 20,000 requests (first 2,000 in brackets):
| Config | JDK 25 | JDK 28-ea |
|---|---|---|
| defaults | 9708 (2678) | 9912 (2567) |
-Xms3g |
9193 (2606) | 9608 (2431) |
-Xms64m |
9247 (2442) | 9893 (2590) |
Raw: output/03-warmup.txt.
What the numbers say, and do not say
- A bigger initial heap costs startup time, and
AlwaysPreTouchcosts the most (about +1.2 s to +1.6 s on a 3 GB heap here). - On this small box the effect is a fraction of Spring Boot's own ~3.3 s startup. JEP 545 describes a much larger effect on big heaps (about 0.1 s at 2 MB versus over 10 s at 32 GB). This machine cannot reproduce that, and these numbers do not test it.
- The first batch of 2,000 requests takes about 2.4 to 2.7 s in every configuration, so for this app warmup is dominated by JIT and class loading, not heap growth.
- Under ZGC,
InitialHeapSizeergonomics are 98,566,144 bytes on JDK 25 but 8,388,608 bytes on JDK 27 and 28-ea (output/01-flag-probe.txt). I did not find which change causes that, so the post reports it as an observation only. - Timings drift between runs. Treat them as indicative, not as a result.
Licence
MIT, as the parent repository.