1
0
Files
zgc-jdk25-benchmarks/scripts/run-all.sh
Ankur e189da79ba Generational ZGC vs G1 on JDK 25: benchmarks, internals and captured results
Complete companion suite for the ankurm.com guide. Everything was compiled and
executed on java 25.0.3+9-LTS-195 (AMD Ryzen 5 5600U, Windows 11); every file
under results/ is unedited program output.

Code
  latency/    open-loop latency harness that measures from intended arrival, so
              coordinated omission is accounted for rather than hidden. Reports
              response time and service time side by side; the gap reached 2910x
              on G1.
  jmh/        four microbenchmarks isolating one mechanism each: TLAB allocation,
              the ZGC load barrier (with a primitive-load control), the write
              barrier (with null / old-to-old / primitive controls), and promotion
              pressure.
  tuning/     provokes ZGC allocation stalls and reads its own JFR recording back,
              grouped by page class. jdk.ZAllocationStall is enabled without a
              threshold, because the 10 ms default hides most stalls.
  internals/  ZGC page classes vs G1 humongous, read from the live VM via
              HotSpotDiagnosticMXBean and jdk.ZPageAllocation events.
  analysis/   unified GC log parser that keeps stop-the-world pauses and
              concurrent phases in separate buckets.
  env/        environment capture; proves generational mode from JMX bean names.

Docs
  Eight chapters covering the JEP 439/474/490 timeline, colored pointers and both
  barriers, allocation stalls, a full flag reference, logging and JFR, benchmark
  methodology, corner cases, and a decision procedure.

Headline result (60 s, 20k req/s, 2 GB heap, ~585 MB live)
  p50               ZGC 0.006 ms   G1 0.005 ms
  p99.9             ZGC 1.437 ms   G1 95.169 ms
  total STW time    ZGC 1.503 ms   G1 1,139.624 ms
2026-07-31 08:47:16 +05:30

90 lines
3.7 KiB
Bash

#!/usr/bin/env bash
# Linux / macOS equivalent of run-all.ps1.
#
# ./scripts/run-all.sh /path/to/jdk-25 [measureSeconds] [warmupSeconds]
#
# Everything published in the companion blog post came from the PowerShell version on Windows;
# this script produces the same set of files in results/ so the comparison can be repeated on
# server hardware, which is where you should actually make a decision.
set -euo pipefail
JAVA_HOME_ARG="${1:-${JAVA_HOME:-}}"
MEASURE_SECONDS="${2:-60}"
WARMUP_SECONDS="${3:-20}"
if [[ -z "$JAVA_HOME_ARG" ]]; then
echo "usage: $0 /path/to/jdk-25 [measureSeconds] [warmupSeconds]" >&2
exit 2
fi
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
JAVA="$JAVA_HOME_ARG/bin/java"
JAVAC="$JAVA_HOME_ARG/bin/javac"
OUT="$ROOT/out"
RESULTS="$ROOT/results"
LOGS="$ROOT/logs"
mkdir -p "$OUT" "$RESULTS" "$LOGS"
# Fixed heap: -Xms == -Xmx removes heap resizing as a variable.
HEAP=(-Xms2g -Xmx2g)
STALL_HEAP=(-Xms512m -Xmx512m)
echo "==> compiling"
mapfile -t SOURCES < <(find "$ROOT/latency/src" "$ROOT/tuning/src" "$ROOT/internals/src" "$ROOT/analysis/src" -name '*.java')
"$JAVAC" -d "$OUT" "${SOURCES[@]}"
capture() {
local name="$1"; shift
echo "==> $name"
"$@" > "$RESULTS/$name.txt" 2>&1 || true
tail -n 4 "$RESULTS/$name.txt" | sed 's/^/ /'
}
capture 01-env-zgc "$JAVA" -XX:+UseZGC "${HEAP[@]}" "$ROOT/env/Env.java"
capture 01-env-g1 "$JAVA" -XX:+UseG1GC "${HEAP[@]}" "$ROOT/env/Env.java"
capture 02-flags-zgc "$JAVA" -XX:+UseZGC "${HEAP[@]}" -XX:+PrintFlagsFinal -version
capture 02-flags-g1 "$JAVA" -XX:+UseG1GC "${HEAP[@]}" -XX:+PrintFlagsFinal -version
capture 03-latency-zgc "$JAVA" -XX:+UseZGC "${HEAP[@]}" \
"-DwarmupSeconds=$WARMUP_SECONDS" "-Dseconds=$MEASURE_SECONDS" \
"-Xlog:gc*,gc+heap=debug:file=$LOGS/zgc-latency.log:time,uptime,level,tags" \
-cp "$OUT" com.ankurm.zgc.latency.LatencyHarness
capture 03-latency-g1 "$JAVA" -XX:+UseG1GC "${HEAP[@]}" \
"-DwarmupSeconds=$WARMUP_SECONDS" "-Dseconds=$MEASURE_SECONDS" \
"-Xlog:gc*,gc+heap=debug:file=$LOGS/g1-latency.log:time,uptime,level,tags" \
-cp "$OUT" com.ankurm.zgc.latency.LatencyHarness
capture 04-stalls-zgc-baseline "$JAVA" -XX:+UseZGC "${STALL_HEAP[@]}" -Dseconds=20 \
-cp "$OUT" com.ankurm.zgc.tuning.AllocationStallDemo
capture 04-stalls-zgc-softmax "$JAVA" -XX:+UseZGC -XX:SoftMaxHeapSize=400m "${STALL_HEAP[@]}" -Dseconds=20 \
-cp "$OUT" com.ankurm.zgc.tuning.AllocationStallDemo
capture 04-stalls-zgc-spiketolerance "$JAVA" -XX:+UseZGC -XX:ZAllocationSpikeTolerance=5 "${STALL_HEAP[@]}" -Dseconds=20 \
-cp "$OUT" com.ankurm.zgc.tuning.AllocationStallDemo
capture 04-stalls-g1 "$JAVA" -XX:+UseG1GC "${STALL_HEAP[@]}" -Dseconds=20 \
"-Xlog:gc:file=$LOGS/g1-stress.log:time,uptime" \
-cp "$OUT" com.ankurm.zgc.tuning.AllocationStallDemo
capture 05-pages-zgc "$JAVA" -XX:+UseZGC "${HEAP[@]}" -cp "$OUT" com.ankurm.zgc.internals.PageSizeDemo
capture 05-pages-g1 "$JAVA" -XX:+UseG1GC "${HEAP[@]}" -cp "$OUT" com.ankurm.zgc.internals.PageSizeDemo
capture 06-gclog-zgc "$JAVA" -cp "$OUT" com.ankurm.zgc.analysis.GcLogParser "$LOGS/zgc-latency.log"
capture 06-gclog-g1 "$JAVA" -cp "$OUT" com.ankurm.zgc.analysis.GcLogParser "$LOGS/g1-latency.log"
echo "==> building JMH module"
(cd "$ROOT/jmh" && JAVA_HOME="$JAVA_HOME_ARG" mvn -q -B clean package)
# NOTE: the GC flag MUST be inside -jvmArgs. JMH forks a fresh JVM per trial and does not inherit
# flags from the outer `java -jar` command. See docs/06-methodology.md section 6.6.
for gc in UseZGC UseG1GC; do
tag=$([[ "$gc" == "UseZGC" ]] && echo zgc || echo g1)
capture "07-jmh-$tag" "$JAVA" -jar "$ROOT/jmh/target/benchmarks.jar" \
-jvmArgs "-XX:+$gc -Xms2g -Xmx2g" -prof gc -rf json -rff "$RESULTS/07-jmh-$tag.json"
done
echo
echo "All raw output is in $RESULTS"