G1GC 为何让 Arrays.fill 慢 265 倍?
Why is Arrays.fill 265 times slower on G1GC?
在一次看似无聊的基准测试中,我发现同样的 Java 代码在 G1GC 下比 ParallelGC 慢了 265 倍。没有内存分配,也没有 GC 循环,问题出在 JIT 生成的汇编代码上。ParallelGC 的写屏障仅用三条指令,而 G1GC 却插入了二十条指令、四个条件分支和一个完整的内存屏障。这篇文章追踪了从宏观性能差异到单条机器指令的完整过程,揭示了 G1 在并发标记和脏卡队列处理上的复杂逻辑,以及为何在特定场景下这些优化反而成了巨大的性能负担。
这篇文章讲述了如何将那个数字一路追踪到单条机器指令,随后发现这条指令只是答案的一半。
HN 评论区
24- hyperpape
我确实对这个效果很好奇,但我实在没耐心看那些 AI 生成的废话。有没有谁能给个真正靠谱、不胡扯的解释,稍微尊重一下读者?
ChatGPT 免费版稍微没那么烦人的总结:https://chatgpt.com/share/6a9ac7a3-15a0-83eb-8c2a-6f72cd9beb....
买家自慎:这解释在宏观层面说得通,但我还没细想。
- _old_dude_
所有 Java GC 都是分代收集器,它们通过追踪是否存在从老年代指向新生代的引用,来减少标记时间(针对新生代收集)。
这个基准测试在老年代创建了一个数组(因为足够大),并存储了一个对象(分配在新生代)。这会触发每次写入时的 GC 屏障。这在真实应用中非常罕见。
Java 26 之前的 G1 屏障之所以慢,是因为:
- GC 屏障和一些 GC 线程会在同一内存区域(卡表)上进行并发操作
- 屏障本身很大(包含大量汇编指令),因此也干扰了 JIT 执行的循环展开优化
Parallel GC 的屏障很简单,且不关心延迟(循环内没有 GC 检查)。
G1GC 的屏障实现在 Java 26 中已经更改,所以请更新你的 Java 运行时版本,然后继续吧。
- pron
这里有两个实用的教训:
1. 升级你的 JDK 以获得最佳性能(正如文章所说,JDK 26 中这个性能下降问题已经消失)。
2. 不要试图通过对象池来“帮助”GC。修改旧对象可能代价高昂,而分配新对象却很便宜(至少对于那些不需要执行某些异常昂贵初始化操作的对象而言)。