// HACKER NEWS — CYBERSECURITY
Why is Arrays.fill 265 times slower on G1GC?
This article shows some JVM tuning using JVM flags. You should never use any JVM flags without knowing what consequences they may produce.
Most of the flags used here are diagnostic ones, used to understand what is going on. Only one of them is worth considering on production,
and I write about it at the very end.
It started with a benchmark that I expected to be boring. Fill two arrays with a reference, once on G1GC, once on ParallelGC:
There is no allocation in these methods, so there are no GC cycles at all during the measurement. Whatever the difference is,
it cannot be “G1 collects garbage slower”. And yet:
139 milliseconds versus 0.5 millisecond. The same Java code, the same JDK, the same machine. G1 is 265 times slower.
This article is the story of chasing that number down to a single machine instruction, and then finding out that the instruction
was only half of the answer.
The first question is always the same one: which code is hot? Since there is no GC and no allocation, I ran the benchmark
with an assembly-level profiler:
On both GCs the answer is the same, 99.8% of the samples are in the JIT-compiled code of Arrays.fill. There is no VM overhead,
no GC thread stealing the CPU, no safepoints. The mutator thread itself is simply running slow code.
So the difference has to be in what the JIT generated, and the profiler gives us exactly that.
This is an ARM64 machine, so the listings below are ARM64. If you have never read assembly, here is everything you need for this
article. It is genuinely a short list.
Registers are the CPU’s working variables. Think of them as around 30 pre-declared local variables that every method shares: