Add README: headline results, module index, doc index, reference machine
This commit is contained in:
141
README.md
Normal file
141
README.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# Generational ZGC vs G1 on JDK 25 — runnable benchmarks and internals
|
||||||
|
|
||||||
|
Companion repository for **[Generational ZGC on JDK 25: Benchmarks vs G1](https://ankurm.com/generational-zgc-jdk-25-vs-g1/)**
|
||||||
|
on [ankurm.com](https://ankurm.com).
|
||||||
|
|
||||||
|
The blog post covers the concepts most people need. **This repository covers everything** — every
|
||||||
|
flag, every failure mode, every measurement pitfall, with runnable code, real captured output, and
|
||||||
|
the reasoning behind each choice.
|
||||||
|
|
||||||
|
Everything here was compiled and executed on `java 25.0.3+9-LTS-195`. Raw, unedited output for every
|
||||||
|
run is in [`results/`](results).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```console
|
||||||
|
git clone https://ankurm.com/git.app/asmhatre/zgc-jdk25-benchmarks.git
|
||||||
|
cd zgc-jdk25-benchmarks
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
pwsh scripts/run-all.ps1 -JavaHome 'C:\Program Files\Java\jdk-25.0.3'
|
||||||
|
|
||||||
|
# Linux / macOS
|
||||||
|
./scripts/run-all.sh /path/to/jdk-25
|
||||||
|
```
|
||||||
|
|
||||||
|
Or run a single piece — nothing but a JDK 25 is required for anything outside `jmh/`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
javac -d out $(find latency tuning internals analysis -name '*.java')
|
||||||
|
|
||||||
|
java -XX:+UseZGC -Xms2g -Xmx2g -cp out com.ankurm.zgc.latency.LatencyHarness
|
||||||
|
java -XX:+UseG1GC -Xms2g -Xmx2g -cp out com.ankurm.zgc.latency.LatencyHarness
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Headline result
|
||||||
|
|
||||||
|
60 seconds, 20,000 req/s open loop, 2 GB heap, ~585 MB live set, ~328 MB/s allocation rate.
|
||||||
|
Full output: [`results/03-latency-zgc.txt`](results/03-latency-zgc.txt),
|
||||||
|
[`results/03-latency-g1.txt`](results/03-latency-g1.txt).
|
||||||
|
|
||||||
|
| Metric | Generational ZGC | G1 |
|
||||||
|
|---|---|---|
|
||||||
|
| p50 response time | 0.006 ms | **0.005 ms** |
|
||||||
|
| p90 | 0.009 ms | 0.009 ms |
|
||||||
|
| p99 | **0.029 ms** | 8.042 ms |
|
||||||
|
| p99.9 | **1.437 ms** | 95.169 ms |
|
||||||
|
| p99.99 | **23.675 ms** | 126.949 ms |
|
||||||
|
| max | **32.178 ms** | 133.018 ms |
|
||||||
|
| total stop-the-world time | **1.503 ms** | 1,139.624 ms |
|
||||||
|
| longest single pause | **0.054 ms** | 104.449 ms |
|
||||||
|
| Full GCs / evacuation failures | **0 / 0** | 4 / 16 |
|
||||||
|
|
||||||
|
G1 wins the median. ZGC wins everything past p99. The two are indistinguishable up to p90 — which is
|
||||||
|
the part most "ZGC is faster" posts leave out.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What is in here
|
||||||
|
|
||||||
|
### Code
|
||||||
|
|
||||||
|
| Module | What it demonstrates | Deps |
|
||||||
|
|---|---|---|
|
||||||
|
| [`env/Env.java`](env/Env.java) | Environment capture; proves generational mode from JMX bean names | none |
|
||||||
|
| [`latency/`](latency/src/com/ankurm/zgc/latency) | **Open-loop** latency harness with coordinated-omission accounting | none |
|
||||||
|
| [`tuning/AllocationStallDemo.java`](tuning/src/com/ankurm/zgc/tuning/AllocationStallDemo.java) | Provokes and measures ZGC allocation stalls via JFR | none |
|
||||||
|
| [`internals/PageSizeDemo.java`](internals/src/com/ankurm/zgc/internals/PageSizeDemo.java) | ZGC page classes vs G1 humongous, read from the live VM | none |
|
||||||
|
| [`analysis/GcLogParser.java`](analysis/src/com/ankurm/zgc/analysis/GcLogParser.java) | Unified-log parser that keeps pauses and concurrent phases apart | none |
|
||||||
|
| [`jmh/`](jmh/src/main/java/com/ankurm/zgc/jmh) | Throughput and barrier microbenchmarks | JMH 1.37 |
|
||||||
|
|
||||||
|
### JMH benchmarks
|
||||||
|
|
||||||
|
| Class | Isolates |
|
||||||
|
|---|---|
|
||||||
|
| [`AllocationBench`](jmh/src/main/java/com/ankurm/zgc/jmh/AllocationBench.java) | TLAB allocation path at 64 B / 1 KB / 32 KB |
|
||||||
|
| [`LoadBarrierBench`](jmh/src/main/java/com/ankurm/zgc/jmh/LoadBarrierBench.java) | **ZGC's load barrier** — pointer chase vs primitive sum control |
|
||||||
|
| [`StoreBarrierBench`](jmh/src/main/java/com/ankurm/zgc/jmh/StoreBarrierBench.java) | **Write barriers** — old→young vs null vs old→old vs primitive control |
|
||||||
|
| [`PromotionBench`](jmh/src/main/java/com/ankurm/zgc/jmh/PromotionBench.java) | Medium-lived objects; the case where "most objects die young" fails |
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
| Chapter | Covers |
|
||||||
|
|---|---|
|
||||||
|
| [1. The generational model](docs/01-generational-model.md) | JEP 439 → 474 → 490 timeline, what `ZGenerational` does now, why there is no `-Xmn` |
|
||||||
|
| [2. Barriers](docs/02-barriers.md) | Colored pointers, load barrier, store barrier, remembered sets, `ZBufferStoreBarriers` |
|
||||||
|
| [3. Allocation stalls](docs/03-allocation-stalls.md) | **The ZGC failure mode pause charts cannot see**, and the 10 ms JFR threshold that hides it |
|
||||||
|
| [4. Tuning reference](docs/04-tuning-reference.md) | Every ZGC product + diagnostic flag, and the G1 flags that still matter |
|
||||||
|
| [5. Logging and JFR](docs/05-logging-and-jfr.md) | Reading `-Xlog:gc*`, the JMX bean trap, the JFR events worth enabling |
|
||||||
|
| [6. Methodology](docs/06-methodology.md) | Coordinated omission, timer granularity, the JMH `-jvmArgs` fork trap |
|
||||||
|
| [7. Corner cases](docs/07-corner-cases.md) | Compressed oops, large-page fragmentation, virtual thread stacks, containers, `System.gc()` |
|
||||||
|
| [8. Choosing a collector](docs/08-choosing.md) | A decision procedure, sizing guidance, when the answer is neither |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Six findings you will not read elsewhere
|
||||||
|
|
||||||
|
1. **G1's coordinated-omission gap on this workload is 2910x.** A closed-loop harness reports G1's
|
||||||
|
p99.9 as 0.033 ms. It is really 95.169 ms.
|
||||||
|
→ [docs/06-methodology.md §6.1](docs/06-methodology.md)
|
||||||
|
2. **ZGC disables compressed oops.** Every reference is 8 bytes instead of 4, so a reference-dense
|
||||||
|
heap can grow 30–40% on switching collector, before any latency benefit.
|
||||||
|
→ [docs/07-corner-cases.md §7.1](docs/07-corner-cases.md)
|
||||||
|
3. **`jdk.ZAllocationStall` has a 10 ms default JFR threshold.** In a run whose mean stall was
|
||||||
|
4.33 ms, a default profile discards most of them.
|
||||||
|
→ [docs/03-allocation-stalls.md §3.3](docs/03-allocation-stalls.md)
|
||||||
|
4. **`new byte[4*1024*1024]` gets a ZGC Large page, not a Medium one.** The 16-byte array header
|
||||||
|
pushes it over the medium-object limit, and it consumes an 8 MB page.
|
||||||
|
→ [docs/07-corner-cases.md §7.2](docs/07-corner-cases.md)
|
||||||
|
5. **`SoftMaxHeapSize` and `ZAllocationSpikeTolerance` did nothing under sustained overload.**
|
||||||
|
Measured: 32,200 / 31,967 / 32,442 stalls across baseline and both mitigations. They buy headroom
|
||||||
|
for *spikes*; they cannot conjure heap that does not exist.
|
||||||
|
→ [results/04-stalls-zgc-softmax.txt](results/04-stalls-zgc-softmax.txt)
|
||||||
|
6. **The throughput ranking inverts under sustained promotion.** G1 is 6.6% faster with no
|
||||||
|
survivors; ZGC is **10.4% faster** with 262,144 of them. "G1 wins throughput" only holds on the
|
||||||
|
workload that suits generational collection best.
|
||||||
|
→ [results/07-jmh-zgc.txt](results/07-jmh-zgc.txt)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reference machine
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| CPU | AMD Ryzen 5 5600U, 6 cores / 12 threads |
|
||||||
|
| RAM | 15.3 GB |
|
||||||
|
| OS | Windows 11 Home |
|
||||||
|
| JDK | `java 25.0.3+9-LTS-195` (Oracle) |
|
||||||
|
| Heap | `-Xms2g -Xmx2g` for all latency runs; `-Xms512m -Xmx512m` for stall runs |
|
||||||
|
|
||||||
|
A laptop, not a server. The absolute numbers are illustrative; the *relationships* are what
|
||||||
|
generalise. Run it on your own hardware — that is what it is for.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
MIT. See [LICENSE](LICENSE).
|
||||||
Reference in New Issue
Block a user