Java 26: G1 GC Throughput via Reduced Synchronization (JEP 522)
Invisible to your application code, but measurable in your throughput metrics. JEP 522 refactors internal synchronization in the G1 garbage collector to reduce thread contention, delivering higher application throughput on multi-core servers — no JVM flags, no code changes required.
Status: Standard — purely internal G1GC optimization, observable only through benchmarks and GC logs.
Background: How G1GC Tracks References
G1GC divides the heap into fixed-size regions (typically 1–32 MB each). To collect a subset of regions efficiently, it maintains a data structure called the Remembered Set (RSet) — a per-region table that records which objects in other regions point into this region.
When application threads mutate object references (a write barrier fires), the JVM records that mutation into a card table — a coarse bitmap where each “card” represents 512 bytes of heap. Concurrently, G1’s refinement threads scan dirty cards and update the RSets.
The Contention Problem (Pre-JDK 26)
The card table and RSet update paths relied heavily on shared, globally-synchronized data structures:
- Dirty card queues — all mutator threads enqueue dirty cards into shared queues guarded by a global lock
- Hot card detection — identifying frequently-written cards used a global hash table with coarse locking
- Refinement thread coordination — deciding which refinement thread claims which cards involved additional cross-thread synchronization
On modern servers with 32, 64, or 128 cores, this global contention became a measurable bottleneck — especially for workloads with high object mutation rates (caches, message queues, graph databases).
What JEP 522 Changes
JEP 522 refactors these internal paths to eliminate the global lock points:
1. Per-Thread Dirty Card Queues
Instead of all mutator threads contending on a single global queue, each thread now has its own local dirty card buffer. Refinement threads drain these per-thread buffers independently, with minimal coordination.
2. Lock-Free Hot Card Tracking
The hot card detection mechanism is replaced with a lock-free algorithm. Cards that are written frequently (hot cards) are identified and handled differently without a global hash table lock.
3. Refined Thread Work Stealing
Refinement threads use a work-stealing scheduler instead of a coordinator thread assigning work, reducing coordination overhead proportionally with core count.
How to Observe the Improvement
There is no flag to enable or disable JEP 522 — it applies to all G1GC runs in JDK 26. You can measure its effect via:
GC Logs
Enable detailed GC logging to inspect refinement times:
java -XX:+UseG1GC \
-Xlog:gc*:file=gc.log:tags,time,uptime,level \
-jar myapp.jarLook for Concurrent Refinement times in the GC log. With JEP 522 you should see shorter refinement phases under high mutation load.
GC Pause Statistics
java -XX:+UseG1GC \
-Xlog:gc+stats=info \
-jar myapp.jarThe Scan RS and Update RS sub-phases of G1 minor (Young GC) collections should be shorter.
JVM Unified Logging — Key Tags
# See refinement thread activity
-Xlog:gc+refine=debug
# See card table dirty/clean events
-Xlog:gc+barrier=trace
# See remembered set update timing
-Xlog:gc+remset=debugJMH Microbenchmark Pattern
To quantify the improvement on your workload, use a simple JMH benchmark that exercises high mutation rates:
@BenchmarkMode(Mode.Throughput)
@State(Scope.Benchmark)
public class MutationBenchmark {
static final int SIZE = 1_000_000;
Object[] array = new Object[SIZE];
Object[] newObjects;
@Setup(Level.Iteration)
public void setup() {
// Pre-allocate to avoid measuring allocation cost
newObjects = new Object[SIZE];
for (int i = 0; i < SIZE; i++) newObjects[i] = new Object();
}
@Benchmark
public void massiveReferenceUpdates() {
// High object mutation rate — exactly what JEP 522 optimizes
for (int i = 0; i < SIZE; i++) {
array[i] = newObjects[i];
}
}
}# Run on JDK 25 vs JDK 26 and compare ops/sec
java -jar benchmarks.jar MutationBenchmark -f 3 -wi 5 -i 10Who Benefits Most
The improvement is most visible for workloads with high object mutation rates combined with many CPU cores:
| Workload | Why It Benefits |
|---|---|
| In-memory caches (Caffeine, Ehcache) | Constant reference updates as entries are evicted and replaced |
| Message queue consumers | High-throughput reference writes processing incoming messages |
| Graph databases and graph algorithms | Frequent pointer updates across the heap |
| Trading systems | Rapid object churn in order book updates |
| Large Spring applications | Scope proxies and injection containers updating references |
For read-heavy or compute-heavy workloads with low mutation rates, the improvement will be negligible — write barriers simply aren’t in the hot path.
Relationship to Other G1GC Improvements
JEP 522 is one of several ongoing G1GC improvements in recent JDK releases:
| JDK | G1GC Improvement |
|---|---|
| JDK 15 | JEP 379 — ZGC as production feature (alternative for low-latency) |
| JDK 16 | Concurrent thread-stack processing |
| JDK 18 | Reduce region pinning in G1 |
| JDK 26 | JEP 522 — Reduce synchronization in refinement paths |
Should You Switch GC?
JEP 522 improves G1GC — it doesn’t change the trade-offs between collectors:
- G1GC (default): Balanced throughput + pause times, good for most applications
- ZGC / Shenandoah: Sub-millisecond pauses, slightly lower peak throughput — ideal for latency-sensitive workloads
- ParallelGC: Maximum throughput, longer pauses — good for batch processing
If you’re already using G1GC (the default since JDK 9), upgrading to JDK 26 gives you JEP 522’s improvements for free. No flag changes needed.
Verifying You Are Using G1GC
# Check which GC is active
java -XX:+PrintFlagsFinal -version | grep UseG1GC
# bool UseG1GC = true (default since JDK 9)
# Explicitly enable G1GC (already the default)
java -XX:+UseG1GC -jar myapp.jar
# Or verify at runtime via MXBean
ManagementFactory.getGarbageCollectorMXBeans()
.stream()
.map(GarbageCollectorMXBean::getName)
.forEach(System.out::println);
// → G1 Young Generation
// → G1 Old GenerationSummary
JEP 522 is a transparent performance improvement — you don’t configure it, you don’t code for it. Just run JDK 26 with G1GC (which is the default) and collect the benefits. On mutation-heavy workloads on multi-core machines, expect measurable throughput gains with no application changes.