1
0
Files
zgc-jdk25-benchmarks/env/Env.java
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

75 lines
3.8 KiB
Java

// Env.java -- prints everything a GC measurement needs in order to be reproducible.
//
// Run it (JDK 25 single-file source launch, no compile step needed):
//
// java env/Env.java
// java -XX:+UseZGC -Xms2g -Xmx2g env/Env.java
// java -XX:+UseG1GC -Xms2g -Xmx2g env/Env.java
//
// Why this file exists: a GC benchmark without the environment printed next to it
// is an anecdote. Heap size, CPU count, GC selection and the *ergonomic* values the
// JVM picked for you (SoftMaxHeapSize, region size, GC thread counts) all move the
// numbers more than the code under test does.
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.RuntimeMXBean;
public final class Env {
public static void main(String[] args) {
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
System.out.println("=== Runtime ===");
System.out.printf("java.version : %s%n", System.getProperty("java.version"));
System.out.printf("java.vm.name : %s%n", System.getProperty("java.vm.name"));
System.out.printf("java.vm.version : %s%n", System.getProperty("java.vm.version"));
System.out.printf("java.vendor : %s%n", System.getProperty("java.vendor"));
System.out.printf("os.name / os.arch : %s / %s%n",
System.getProperty("os.name"), System.getProperty("os.arch"));
System.out.printf("availableProcessors : %d%n", Runtime.getRuntime().availableProcessors());
System.out.printf("maxMemory (-Xmx) : %s%n", mb(Runtime.getRuntime().maxMemory()));
System.out.printf("totalMemory (now) : %s%n", mb(Runtime.getRuntime().totalMemory()));
System.out.println();
System.out.println("=== Collector in use ===");
// On JDK 25 the ZGC bean names are "ZGC Major Cycles" / "ZGC Minor Cycles" (and the
// matching "... Pauses" beans). The presence of BOTH a Minor and a Major bean is the
// cheapest programmatic proof that you are running *generational* ZGC.
boolean sawMinor = false, sawMajor = false;
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
System.out.printf(" bean: %-24s count=%-6d timeMs=%d%n",
gc.getName(), gc.getCollectionCount(), gc.getCollectionTime());
String n = gc.getName().toLowerCase();
if (n.contains("minor") || n.contains("young")) sawMinor = true;
if (n.contains("major") || n.contains("old") || n.contains("full")) sawMajor = true;
}
System.out.printf(" generational? : %s%n",
(sawMinor && sawMajor) ? "yes (separate young + old cycles reported)" : "no / single cycle");
System.out.println();
System.out.println("=== Memory pools ===");
for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
System.out.printf(" %-28s type=%-10s max=%s%n",
pool.getName(), pool.getType(), mb(pool.getUsage().getMax()));
}
System.out.println();
System.out.println("=== JVM arguments actually in effect ===");
if (runtime.getInputArguments().isEmpty()) {
System.out.println(" (none -- everything below is ergonomic)");
}
runtime.getInputArguments().forEach(a -> System.out.println(" " + a));
System.out.println();
System.out.println("Tip: dump every ergonomic value the JVM chose with");
System.out.println(" java -XX:+UseZGC -Xms2g -Xmx2g -XX:+PrintFlagsFinal -version");
}
private static String mb(long bytes) {
if (bytes < 0) return "unbounded";
return String.format("%,d bytes (%.0f MB)", bytes, bytes / 1024.0 / 1024.0);
}
}