Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HmvoHH3ZnCEaPNXRpDxDZe
63 lines
3.9 KiB
Markdown
63 lines
3.9 KiB
Markdown
# shenandoah-jdk28 — generational Shenandoah vs SATB Shenandoah vs G1 vs ZGC on a Spring Boot service
|
|
|
|
Companion module for the ankurm.com post
|
|
**Generational Shenandoah by Default in JDK 28 (JEP 535): Benchmarked Against G1 and ZGC**.
|
|
There is no `docs/` folder: this README and the captured files in [`output/`](output) are the documentation.
|
|
|
|
## The honest status
|
|
|
|
JEP 535 ("Shenandoah: Generational Mode by Default", which also deprecates the SATB mode) is **Targeted** to JDK 28 on
|
|
openjdk.org, but it is **not integrated in Temurin 28+21 EA**. [`output/01-gc-mode-probe.txt`](output/01-gc-mode-probe.txt)
|
|
shows `-XX:+UseShenandoahGC` still starts in *Snapshot-At-The-Beginning (SATB)* mode on both JDK 25.0.5+8 and 28+21.
|
|
Generational mode is available on both as an explicit opt-in (`-XX:ShenandoahGCMode=generational`).
|
|
So this module benchmarks **explicit generational vs explicit SATB**, which is what the default will switch between.
|
|
Oracle's jdk.java.net builds do not contain Shenandoah at all; use Temurin (or another build that ships it).
|
|
|
|
## Versions
|
|
|
|
| Component | Version |
|
|
|---|---|
|
|
| JDK 25 | Temurin 25.0.5+8 (probe only) |
|
|
| JDK 28 | Temurin 28+21 EA (28-beta+21-ea, build 202610221655) |
|
|
| Spring Boot | 4.1.2 (`spring-boot-starter-webmvc`) |
|
|
| Machine | 2 vCPU, 8 GB RAM Linux sandbox. A small box: absolute numbers are illustrative |
|
|
| Heap | `-Xms1g -Xmx1g` for every configuration |
|
|
|
|
## Quick start
|
|
|
|
```console
|
|
mvn -B -DskipTests package
|
|
export J25=/path/to/temurin-25 J28=/path/to/temurin-28-ea
|
|
scripts/run-all.sh # probe + 3 interleaved reps of 4 configurations, ~2.5 min per run
|
|
scripts/more-reps.sh 4 5 # append more reps and re-summarise
|
|
```
|
|
|
|
## What is measured
|
|
|
|
| File | What it is |
|
|
|---|---|
|
|
| [`src/main/java/com/ankurm/shenandoah/ShenandoahApplication.java`](src/main/java/com/ankurm/shenandoah/ShenandoahApplication.java) | `/work` replaces 8 of 200,000 1 KiB cache slots and allocates 100 short-lived 1 KiB arrays; `/info` reports the heap |
|
|
| [`scripts/LoadGen.java`](scripts/LoadGen.java) | `open`: fixed-rate load (3,000 req/s, 64 virtual threads), latency measured from the *intended* send time; `closed`: peak throughput |
|
|
| [`scripts/bench-one.sh`](scripts/bench-one.sh) | One run: 30 s warm-up, 60 s open-loop window, 20 s closed loop; pauses from `-Xlog:gc*`; peak RSS from `VmHWM` |
|
|
| [`scripts/gc_pauses.py`](scripts/gc_pauses.py) | Stop-the-world pauses inside the measure window |
|
|
| [`scripts/summarize.py`](scripts/summarize.py) | Median per configuration |
|
|
|
|
## Results (median of 5 interleaved runs, JDK 28+21 EA)
|
|
|
|
[`output/03-summary.txt`](output/03-summary.txt), from the per-run lines in [`output/02-runs.txt`](output/02-runs.txt):
|
|
|
|
| Configuration | p50 ms | p99 ms | p99.9 ms | max ms | peak req/s | pause max ms | RSS MB |
|
|
|---|---|---|---|---|---|---|---|
|
|
| G1 | 0.63 | 29.2 | 128.1 | 179.5 | 8,900 | 111.6 | 1,205 |
|
|
| ZGC (generational) | 0.60 | 7.4 | 17.3 | 36.4 | 7,117 | 0.17 | 1,250 |
|
|
| Shenandoah SATB | 0.78 | 14.9 | 43.6 | 86.4 | 5,026 | 1.5 | 1,212 |
|
|
| Shenandoah generational | 0.82 | 1,557.5 | 1,820.4 | 1,864.7 | 5,498 | 2.6 | 1,227 |
|
|
|
|
**Read the last row carefully.** Per-run p99 for generational Shenandoah was 1,557, 3,077, 24.5, 1,658 and 692 ms: four of five
|
|
runs contain a multi-second latency episode while the recorded stop-the-world pauses stay at a few milliseconds
|
|
(one run had a 209 ms `Pause Full`). The GC logs do not show allocation failures (`Trigger: Failed to allocate` count is 0), and
|
|
the `Cancelling GC: Concurrent GC` lines are old-generation marking being preempted for a young collection, which is normal.
|
|
So the cause of the stalls is **not established** here. One SATB run (811 ms p99) also stalled with no GC event at all,
|
|
which says this 2-vCPU machine produces stalls of its own. Treat the generational result as "did not behave on this box
|
|
and this workload", not as a general verdict. A larger machine and a bigger heap are the obvious next experiment.
|