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 |
|
||||
| [`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
|
||||
|
||||
|
||||
@@ -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