Add jdk28-zgc: ZGC startup and warmup baseline on JDK 25/27/28-ea, and a probe for JEP 545/546

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
This commit is contained in:
2026-10-12 17:13:14 +00:00
co-authored by Claude Sonnet 5.5
parent 2f88734f5d
commit df13a904d7
11 changed files with 431 additions and 0 deletions
+1
View File
@@ -73,6 +73,7 @@ the part most "ZGC is faster" posts leave out.
| [`jmh/`](jmh/src/main/java/com/ankurm/zgc/jmh) | Throughput and barrier microbenchmarks | JMH 1.37 |
| [`jdk27-memory/`](jdk27-memory) | Spring Boot 4.1 service measured on JDK 25, 26 and 27: compact object headers (JEP 534) and G1 everywhere (JEP 523), heap footprint, container sizing with real cgroup limits. Its README and `output/` replace a `docs/` folder; the write-up is [the post](https://ankurm.com/jdk-27-compact-object-headers-g1-default-memory-spring-boot/) | Spring Boot 4.1.1, three JDKs |
| [`flags/`](flags) | The flag-by-flag production cheat sheet: `MaxRAMPercentage` vs a hardcoded `-Xmx` (one fails safely, one gets the container SIGKILLed), `-XX:-UseContainerSupport`, the `ActiveProcessorCount` lie, and which diagnostic flags (`HeapDumpOnOutOfMemoryError`, `NativeMemoryTracking`) are worth leaving on in prod — on real Docker containers, JDK 25 and JDK 27. Its README and `docs/output/` replace a `docs/` folder; the write-up is [the post](https://ankurm.com/jvm-flags-for-spring-boot-in-containers-jdk-25-27/) | Spring Boot 4.1.1, Docker, two JDKs |
| [`jdk28-zgc/`](jdk28-zgc) | ZGC startup and warmup versus initial heap size on JDK 25, 27 and 28 early access with a Spring Boot 4.1.1 server, plus a probe showing that JEP 545/546's `-XX:+ZAdaptiveHeapSizing` is not in any build yet. Its README and `output/` replace a `docs/` folder; the write-up is [the post](https://ankurm.com/zgc-jdk-28-adaptive-heap-sizing-faster-startup/) | Spring Boot 4.1.1, three JDKs |
### JMH benchmarks
+78
View File
@@ -0,0 +1,78 @@
# 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/`](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`](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
```console
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`](src/main/java/com/ankurm/zgc/DemoApplication.java) | One server: `/hello` allocates 16 KB, `/heap` reports committed and max heap |
| [`scripts/startup.sh`](scripts/startup.sh) | Wall-clock ms from process start to first HTTP 200, repeated, with min/median/max |
| [`scripts/warmup.sh`](scripts/warmup.sh) + [`scripts/Warmup.java`](scripts/Warmup.java) | 20,000 requests, 4 threads, timing each batch of 2,000; client is always JDK 25 |
| [`scripts/run-all.sh`](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`](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`](output/03-warmup.txt).
## What the numbers say, and do not say
- A bigger initial heap costs startup time, and `AlwaysPreTouch` costs 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, `InitialHeapSize` ergonomics are 98,566,144 bytes on JDK 25 but 8,388,608 bytes on JDK 27 and 28-ea
([`output/01-flag-probe.txt`](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.
+30
View File
@@ -0,0 +1,30 @@
== JDK 25
openjdk version "25.0.4.1" 2026-08-18 LTS
size_t InitialHeapSize = 98566144 {product} {ergonomic}
size_t MaxHeapSize = 1568669696 {product} {ergonomic}
size_t SoftMaxHeapSize = 1411802726 {manageable} {ergonomic}
bool ZUncommit = true {product} {default}
uintx ZUncommitDelay = 300 {product} {default}
-- probing -XX:+ZAdaptiveHeapSizing
Unrecognized VM option 'ZAdaptiveHeapSizing'
== JDK 27
openjdk version "27" 2026-09-15
size_t InitialHeapSize = 8388608 {product} {ergonomic}
size_t MaxHeapSize = 1568669696 {product} {ergonomic}
size_t SoftMaxHeapSize = 1411802726 {manageable} {ergonomic}
bool ZUncommit = true {product} {default}
uintx ZUncommitDelay = 300 {product} {default}
-- probing -XX:+ZAdaptiveHeapSizing
Unrecognized VM option 'ZAdaptiveHeapSizing'
== JDK 28-ea
openjdk version "28-ea" 2027-03-23
size_t InitialHeapSize = 8388608 {product} {ergonomic}
size_t MaxHeapSize = 1568669696 {product} {ergonomic}
size_t SoftMaxHeapSize = 1411802726 {manageable} {ergonomic}
bool ZUncommit = true {product} {default}
uintx ZUncommitDelay = 300 {product} {default}
-- probing -XX:+ZAdaptiveHeapSizing
Unrecognized VM option 'ZAdaptiveHeapSizing'
+90
View File
@@ -0,0 +1,90 @@
jdk25 zgc default run 1: first-response 3439 ms, committed heap 156 MiB
jdk25 zgc default run 2: first-response 3596 ms, committed heap 154 MiB
jdk25 zgc default run 3: first-response 3325 ms, committed heap 154 MiB
jdk25 zgc default run 4: first-response 3529 ms, committed heap 154 MiB
jdk25 zgc default run 5: first-response 3251 ms, committed heap 154 MiB
jdk25 zgc default SUMMARY: min 3251 ms, median 3439 ms, max 3596 ms (n=5)
jdk25 zgc Xms64m run 1: first-response 3291 ms, committed heap 158 MiB
jdk25 zgc Xms64m run 2: first-response 3233 ms, committed heap 154 MiB
jdk25 zgc Xms64m run 3: first-response 3299 ms, committed heap 154 MiB
jdk25 zgc Xms64m run 4: first-response 3488 ms, committed heap 156 MiB
jdk25 zgc Xms64m run 5: first-response 3396 ms, committed heap 156 MiB
jdk25 zgc Xms64m SUMMARY: min 3233 ms, median 3299 ms, max 3488 ms (n=5)
jdk25 zgc Xms1g run 1: first-response 3502 ms, committed heap 1024 MiB
jdk25 zgc Xms1g run 2: first-response 3475 ms, committed heap 1024 MiB
jdk25 zgc Xms1g run 3: first-response 3478 ms, committed heap 1024 MiB
jdk25 zgc Xms1g run 4: first-response 3728 ms, committed heap 1024 MiB
jdk25 zgc Xms1g run 5: first-response 3395 ms, committed heap 1024 MiB
jdk25 zgc Xms1g SUMMARY: min 3395 ms, median 3478 ms, max 3728 ms (n=5)
jdk25 zgc Xms3g run 1: first-response 3836 ms, committed heap 3072 MiB
jdk25 zgc Xms3g run 2: first-response 4070 ms, committed heap 3072 MiB
jdk25 zgc Xms3g run 3: first-response 3886 ms, committed heap 3072 MiB
jdk25 zgc Xms3g run 4: first-response 3717 ms, committed heap 3072 MiB
jdk25 zgc Xms3g run 5: first-response 3827 ms, committed heap 3072 MiB
jdk25 zgc Xms3g SUMMARY: min 3717 ms, median 3836 ms, max 4070 ms (n=5)
jdk25 zgc Xms3g pretouch run 1: first-response 5058 ms, committed heap 3072 MiB
jdk25 zgc Xms3g pretouch run 2: first-response 4941 ms, committed heap 3072 MiB
jdk25 zgc Xms3g pretouch run 3: first-response 4801 ms, committed heap 3072 MiB
jdk25 zgc Xms3g pretouch run 4: first-response 4834 ms, committed heap 3072 MiB
jdk25 zgc Xms3g pretouch run 5: first-response 4626 ms, committed heap 3072 MiB
jdk25 zgc Xms3g pretouch SUMMARY: min 4626 ms, median 4834 ms, max 5058 ms (n=5)
jdk27 zgc default run 1: first-response 3598 ms, committed heap 148 MiB
jdk27 zgc default run 2: first-response 3421 ms, committed heap 148 MiB
jdk27 zgc default run 3: first-response 3510 ms, committed heap 148 MiB
jdk27 zgc default run 4: first-response 3280 ms, committed heap 152 MiB
jdk27 zgc default run 5: first-response 3239 ms, committed heap 150 MiB
jdk27 zgc default SUMMARY: min 3239 ms, median 3421 ms, max 3598 ms (n=5)
jdk27 zgc Xms64m run 1: first-response 3276 ms, committed heap 152 MiB
jdk27 zgc Xms64m run 2: first-response 3252 ms, committed heap 146 MiB
jdk27 zgc Xms64m run 3: first-response 3348 ms, committed heap 150 MiB
jdk27 zgc Xms64m run 4: first-response 3479 ms, committed heap 146 MiB
jdk27 zgc Xms64m run 5: first-response 3156 ms, committed heap 152 MiB
jdk27 zgc Xms64m SUMMARY: min 3156 ms, median 3276 ms, max 3479 ms (n=5)
jdk27 zgc Xms1g run 1: first-response 3165 ms, committed heap 1024 MiB
jdk27 zgc Xms1g run 2: first-response 3475 ms, committed heap 1024 MiB
jdk27 zgc Xms1g run 3: first-response 3412 ms, committed heap 1024 MiB
jdk27 zgc Xms1g run 4: first-response 3275 ms, committed heap 1024 MiB
jdk27 zgc Xms1g run 5: first-response 3237 ms, committed heap 1024 MiB
jdk27 zgc Xms1g SUMMARY: min 3165 ms, median 3275 ms, max 3475 ms (n=5)
jdk27 zgc Xms3g run 1: first-response 4142 ms, committed heap 3072 MiB
jdk27 zgc Xms3g run 2: first-response 3846 ms, committed heap 3072 MiB
jdk27 zgc Xms3g run 3: first-response 3618 ms, committed heap 3072 MiB
jdk27 zgc Xms3g run 4: first-response 3563 ms, committed heap 3072 MiB
jdk27 zgc Xms3g run 5: first-response 3593 ms, committed heap 3072 MiB
jdk27 zgc Xms3g SUMMARY: min 3563 ms, median 3618 ms, max 4142 ms (n=5)
jdk27 zgc Xms3g pretouch run 1: first-response 4782 ms, committed heap 3072 MiB
jdk27 zgc Xms3g pretouch run 2: first-response 4560 ms, committed heap 3072 MiB
jdk27 zgc Xms3g pretouch run 3: first-response 4385 ms, committed heap 3072 MiB
jdk27 zgc Xms3g pretouch run 4: first-response 4532 ms, committed heap 3072 MiB
jdk27 zgc Xms3g pretouch run 5: first-response 4623 ms, committed heap 3072 MiB
jdk27 zgc Xms3g pretouch SUMMARY: min 4385 ms, median 4560 ms, max 4782 ms (n=5)
jdk28ea zgc default run 1: first-response 3439 ms, committed heap 144 MiB
jdk28ea zgc default run 2: first-response 3918 ms, committed heap 144 MiB
jdk28ea zgc default run 3: first-response 3290 ms, committed heap 144 MiB
jdk28ea zgc default run 4: first-response 3420 ms, committed heap 146 MiB
jdk28ea zgc default run 5: first-response 3170 ms, committed heap 146 MiB
jdk28ea zgc default SUMMARY: min 3170 ms, median 3420 ms, max 3918 ms (n=5)
jdk28ea zgc Xms64m run 1: first-response 3350 ms, committed heap 146 MiB
jdk28ea zgc Xms64m run 2: first-response 3217 ms, committed heap 148 MiB
jdk28ea zgc Xms64m run 3: first-response 3367 ms, committed heap 148 MiB
jdk28ea zgc Xms64m run 4: first-response 3274 ms, committed heap 150 MiB
jdk28ea zgc Xms64m run 5: first-response 3522 ms, committed heap 150 MiB
jdk28ea zgc Xms64m SUMMARY: min 3217 ms, median 3350 ms, max 3522 ms (n=5)
jdk28ea zgc Xms1g run 1: first-response 3458 ms, committed heap 1024 MiB
jdk28ea zgc Xms1g run 2: first-response 3733 ms, committed heap 1024 MiB
jdk28ea zgc Xms1g run 3: first-response 3478 ms, committed heap 1024 MiB
jdk28ea zgc Xms1g run 4: first-response 3642 ms, committed heap 1024 MiB
jdk28ea zgc Xms1g run 5: first-response 4136 ms, committed heap 1024 MiB
jdk28ea zgc Xms1g SUMMARY: min 3458 ms, median 3642 ms, max 4136 ms (n=5)
jdk28ea zgc Xms3g run 1: first-response 3590 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g run 2: first-response 3871 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g run 3: first-response 3803 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g run 4: first-response 3691 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g run 5: first-response 3857 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g SUMMARY: min 3590 ms, median 3803 ms, max 3871 ms (n=5)
jdk28ea zgc Xms3g pretouch run 1: first-response 5010 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g pretouch run 2: first-response 4882 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g pretouch run 3: first-response 5040 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g pretouch run 4: first-response 5283 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g pretouch run 5: first-response 4764 ms, committed heap 3072 MiB
jdk28ea zgc Xms3g pretouch SUMMARY: min 4764 ms, median 5010 ms, max 5283 ms (n=5)
+24
View File
@@ -0,0 +1,24 @@
== jdk25 zgc default
batch ms: 2678 1147 993 749 829 667 715 591 745 588
total ms: 9708
committed after load: 342 MiB (max 1496 MiB)
== jdk25 zgc Xms3g
batch ms: 2606 1155 909 958 748 674 546 409 637 548
total ms: 9193
committed after load: 3072 MiB (max 3072 MiB)
== jdk25 zgc Xms64m
batch ms: 2442 1221 971 810 997 689 648 548 480 437
total ms: 9247
committed after load: 312 MiB (max 1496 MiB)
== jdk28ea zgc default
batch ms: 2567 1217 977 979 907 826 730 590 602 512
total ms: 9912
committed after load: 284 MiB (max 1496 MiB)
== jdk28ea zgc Xms3g
batch ms: 2431 1282 1112 947 716 709 651 631 566 559
total ms: 9608
committed after load: 3072 MiB (max 3072 MiB)
== jdk28ea zgc Xms64m
batch ms: 2590 1182 1076 886 796 704 779 717 561 598
total ms: 9893
committed after load: 414 MiB (max 1496 MiB)
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
<relativePath/>
</parent>
<groupId>com.ankurm</groupId>
<artifactId>jdk28-zgc</artifactId>
<version>1.0.0</version>
<name>jdk28-zgc</name>
<description>Startup and heap-sizing harness for ZGC across JDK 25, 27 and 28 early access</description>
<properties>
<java.version>25</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
+37
View File
@@ -0,0 +1,37 @@
import java.net.URI;
import java.net.http.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
/** Fires TOTAL requests with THREADS threads and reports how long each batch of BATCH took. */
public class Warmup {
public static void main(String[] a) throws Exception {
int port = Integer.parseInt(a[0]);
int total = 20_000, batch = 2_000, threads = 4;
HttpClient c = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpRequest rq = HttpRequest.newBuilder(URI.create("http://localhost:" + port + "/hello")).build();
AtomicInteger done = new AtomicInteger();
long[] batchEnd = new long[total / batch];
long t0 = System.nanoTime();
ExecutorService ex = Executors.newFixedThreadPool(threads);
List<Future<?>> fs = new ArrayList<>();
for (int t = 0; t < threads; t++) fs.add(ex.submit(() -> {
try {
while (true) {
int n = done.incrementAndGet();
if (n > total) return null;
c.send(rq, HttpResponse.BodyHandlers.discarding());
if (n % batch == 0) batchEnd[n / batch - 1] = System.nanoTime();
}
} catch (Exception e) { throw new RuntimeException(e); }
}));
for (Future<?> f : fs) f.get();
ex.shutdown();
StringBuilder sb = new StringBuilder("batch ms:");
long prev = t0;
for (long e : batchEnd) { sb.append(' ').append((e - prev) / 1_000_000); prev = e; }
System.out.println(sb);
System.out.println("total ms: " + (prev - t0) / 1_000_000);
}
}
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Regenerates every file in output/. Set J25, J27, J28 to the three JDK home directories.
# Timing results drift between runs and machines; treat them as indicative, not exact.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
OUT="$HERE/../output"; mkdir -p "$OUT"
: "${J25:?}" "${J27:?}" "${J28:?}"
export JDK_CLIENT="$J25"
RUNS=${RUNS:-5}
# 01 - what does each JDK say about the ZGC flags the two JEPs talk about?
{
for p in "25|$J25" "27|$J27" "28-ea|$J28"; do
v=${p%%|*}; h=${p#*|}
echo "== JDK $v"
"$h/bin/java" -version 2>&1 | grep -v Picked | sed -n 1p
"$h/bin/java" -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -XX:+PrintFlagsFinal -version 2>&1 \
| grep -E ' (InitialHeapSize|MaxHeapSize|SoftMaxHeapSize|ZUncommit|ZUncommitDelay) ' | sed 's/ \{2,\}/ /g'
echo "-- probing -XX:+ZAdaptiveHeapSizing"
"$h/bin/java" -XX:+UseZGC -XX:+ZAdaptiveHeapSizing -version 2>&1 | grep -v Picked | sed -n 1p
echo
done
} > "$OUT/01-flag-probe.txt"
# 02 - startup versus initial heap size
{
for p in "jdk25|$J25" "jdk27|$J27" "jdk28ea|$J28"; do
l=${p%%|*}; h=${p#*|}
"$HERE/startup.sh" "$h" "$l zgc default" "$RUNS" -XX:+UseZGC
"$HERE/startup.sh" "$h" "$l zgc Xms64m" "$RUNS" -XX:+UseZGC -Xms64m
"$HERE/startup.sh" "$h" "$l zgc Xms1g" "$RUNS" -XX:+UseZGC -Xms1g
"$HERE/startup.sh" "$h" "$l zgc Xms3g" "$RUNS" -XX:+UseZGC -Xms3g
"$HERE/startup.sh" "$h" "$l zgc Xms3g pretouch" "$RUNS" -XX:+UseZGC -Xms3g -XX:+AlwaysPreTouch
done
} > "$OUT/02-startup-vs-xms.txt" 2>&1
# 03 - warmup: how long do the first 20,000 requests take?
{
for p in "jdk25|$J25" "jdk28ea|$J28"; do
l=${p%%|*}; h=${p#*|}
"$HERE/warmup.sh" "$h" "$l zgc default" -XX:+UseZGC
"$HERE/warmup.sh" "$h" "$l zgc Xms3g" -XX:+UseZGC -Xms3g
"$HERE/warmup.sh" "$h" "$l zgc Xms64m" -XX:+UseZGC -Xms64m
done
} > "$OUT/03-warmup.txt" 2>&1
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Usage: startup.sh <java-home> <label> <runs> [jvm args...]
# Starts the Spring Boot jar, polls /hello until HTTP 200, and prints wall-clock ms from
# process start to first response plus the committed heap one second later. One line per run,
# then a summary line (min / median / max).
set -u
JH=$1; LABEL=$2; RUNS=$3; shift 3
JAR="$(dirname "$0")/../target/jdk28-zgc-1.0.0.jar"
PORT=${PORT:-18080}
times=()
for r in $(seq 1 "$RUNS"); do
t0=$(date +%s%N)
"$JH/bin/java" "$@" -jar "$JAR" --server.port=$PORT --spring.main.banner-mode=off \
--logging.level.root=WARN > /dev/null 2>&1 &
PID=$!
while :; do
code=$(curl -s -o /dev/null -m 1 -w '%{http_code}' "localhost:$PORT/hello")
[ "$code" = 200 ] && break
kill -0 $PID 2>/dev/null || { echo "$LABEL run $r: JVM exited early"; break; }
sleep 0.02
done
t1=$(date +%s%N)
ms=$(( (t1 - t0) / 1000000 ))
sleep 1
committed=$(curl -s "localhost:$PORT/heap" | sed -n 's/.*"committedMiB":\([0-9]*\).*/\1/p')
echo "$LABEL run $r: first-response ${ms} ms, committed heap ${committed} MiB"
times+=("$ms")
kill $PID 2>/dev/null; wait $PID 2>/dev/null
done
sorted=($(printf '%s\n' "${times[@]}" | sort -n))
n=${#sorted[@]}
echo "$LABEL SUMMARY: min ${sorted[0]} ms, median ${sorted[$((n/2))]} ms, max ${sorted[$((n-1))]} ms (n=$n)"
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Usage: warmup.sh <java-home> <label> [jvm args...]
# Starts the server, waits for the first response, then runs Warmup.java (always on the same
# client JDK, JDK_CLIENT) so only the server JVM changes between runs.
set -u
JH=$1; LABEL=$2; shift 2
HERE="$(cd "$(dirname "$0")" && pwd)"
JAR="$HERE/../target/jdk28-zgc-1.0.0.jar"
PORT=${PORT:-18080}
CLIENT=${JDK_CLIENT:-$JH}
"$JH/bin/java" "$@" -jar "$JAR" --server.port=$PORT --spring.main.banner-mode=off \
--logging.level.root=WARN > /dev/null 2>&1 &
PID=$!
until [ "$(curl -s -o /dev/null -m 1 -w '%{http_code}' localhost:$PORT/hello)" = 200 ]; do sleep 0.02; done
echo "== $LABEL"
"$CLIENT/bin/java" "$HERE/Warmup.java" $PORT 2>&1 | grep -v Picked
curl -s "localhost:$PORT/heap" | sed -n 's/.*"committedMiB":\([0-9]*\),"maxMiB":\([0-9]*\).*/committed after load: \1 MiB (max \2 MiB)/p'; echo
kill $PID 2>/dev/null; wait $PID 2>/dev/null
@@ -0,0 +1,43 @@
package com.ankurm.zgc;
import java.lang.management.ManagementFactory;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/** Smallest useful server: one cheap endpoint, one endpoint that reports heap state. */
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@RestController
static class Api {
private final byte[][] garbage = new byte[64][];
private int i;
/** Allocates a little so ZGC has something to do. */
@GetMapping("/hello")
String hello() {
garbage[i++ & 63] = new byte[16 * 1024];
return "ok";
}
@GetMapping("/heap")
Map<String, Object> heap() {
Runtime rt = Runtime.getRuntime();
Map<String, Object> m = new LinkedHashMap<>();
m.put("javaVersion", System.getProperty("java.version"));
m.put("committedMiB", rt.totalMemory() >> 20);
m.put("maxMiB", rt.maxMemory() >> 20);
m.put("gc", ManagementFactory.getGarbageCollectorMXBeans().stream().map(b -> b.getName()).toList());
m.put("jvmUptimeMs", ManagementFactory.getRuntimeMXBean().getUptime());
return m;
}
}
}