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:
@@ -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 |
|
| [`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 |
|
| [`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 |
|
| [`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
|
### JMH benchmarks
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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'
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+45
@@ -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
|
||||||
Executable
+32
@@ -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)"
|
||||||
Executable
+18
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user