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.jar

Look 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.jar

The 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=debug

JMH 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 10

Who 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 Generation

Summary

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.