Why Arrays.fill is 265 times slower on G1GC
Why is Arrays.fill 265 times slower on G1GC?
A JMH benchmark reveals that filling an array with a reference is 265 times slower on G1GC than on ParallelGC, despite no allocations or GC cycles. The author traces the issue to the G1 write barrier's extra instructions and a full memory fence, and explains how the JIT's optimization choices—like loop unrolling—interact with the barrier's early exits. The article provides a detailed assembly-level analysis on ARM64, with insights into JVM internals and a caution about using diagnostic flags.
The same Java code, the same JDK, the same machine. G1 is 265 times slower.
- hyperpape
I'm genuinely curious about the effect, but I simply don't have patience for the AI writing. Can anyone give an actual non-garbage explanation with some respect for the reader?
Slightly less annoying summary from ChatGPT free: https://chatgpt.com/share/6a9ac7a3-15a0-83eb-8c2a-6f72cd9beb....
Caveat emptor: it makes high level sense, but I haven’t thought about it in detail.
- _old_dude_
All Java GCs are generational collectors, they reduce the marking time (for young collection) by tracking if there is a reference from the old generation to the new generation.
The benchmark creates an array in the old generation (by being big enough) and stores an object (allocated in the new generation). This triggers the GC barrier for every writes. Something rare in real application.
The G1 barrier before Java 26 is slow because:
- the GC barrier and some GC threads do concurrent operations on the same memory zone (the card table)
- the barrier is big (a lot of assembler instructions) so it also troubles the loop unrolling optimization performed by JITs
Parallel GC has a simple barrier and do not care about latency (no GC check inside the loop).
The barrier implementation of G1GC was changed in Java 26, so update your Java runtime version and move on.
- pron
There are two practical lessons here:
1. Upgrade your JDK for the best performance (as the article says, the slowdown is gone in JDK 26).
2. Don't try to help the GC by pooling objects. Mutating old objects can be expensive, while allocating new ones is cheap (at least for objects that don't do some exceptionally expensive initialisation).