如何通过JVM卡表分代扫描,让Partial GC在毫秒级内完成改写?

2026-04-24 17:190阅读0评论SEO基础
  • 内容介绍
  • 相关推荐

本文共计962个文字,预计阅读时间需要4分钟。

如何通过JVM卡表分代扫描,让Partial GC在毫秒级内完成改写?

卡表本质上是用于空间换时间的索引结构。它不保证100%的精确度(可能会存在错误标记),但极大缩小了扫描范围。在测试中,一次Minor GC对老年代扫描量的通通常只有几百KB到几MB,而非整个GB级的老年代。

卡表怎么被更新?写屏障是幕后推手

卡表不会自动同步,靠的是写屏障(Write Barrier)——每次 JVM 执行类似 obj.field = otherObj 这样的赋值操作时,如果 otherObj 在新生代、而 obj 在老年代,JVM 就会触发写屏障,把 obj 所在卡页对应的位置在卡表中设为 dirty(比如置为 1)。

注意几个关键点:

  • 写屏障只在**老年代对象引用新生代对象**时触发,反向不触发
  • HotSpot 默认使用的是「后写屏障」(Post-write barrier),开销极小,现代 CPU 流水线可很好掩盖
  • 如果关闭写屏障(如用 -XX:-UseCondCardMark),卡表可能漏标,导致 Minor GC 漏掉存活对象,最终引发错误回收

卡表失效的典型场景:CMS 和 G1 的不同应对

卡表不是万能的。当发生并发修改(比如用户线程一边写、GC 线程一边扫描卡表),就可能出现“漏标”。CMS 和 G1 的处理方式直接体现了卡表的局限性:

CMS 使用增量更新(Incremental Update):一旦发现老年代对象新增了对新生代的引用,就把它重新标灰,后续再扫——这会增加 Minor GC 的扫描工作量,但保证不漏。

阅读全文

本文共计962个文字,预计阅读时间需要4分钟。

如何通过JVM卡表分代扫描,让Partial GC在毫秒级内完成改写?

卡表本质上是用于空间换时间的索引结构。它不保证100%的精确度(可能会存在错误标记),但极大缩小了扫描范围。在测试中,一次Minor GC对老年代扫描量的通通常只有几百KB到几MB,而非整个GB级的老年代。

卡表怎么被更新?写屏障是幕后推手

卡表不会自动同步,靠的是写屏障(Write Barrier)——每次 JVM 执行类似 obj.field = otherObj 这样的赋值操作时,如果 otherObj 在新生代、而 obj 在老年代,JVM 就会触发写屏障,把 obj 所在卡页对应的位置在卡表中设为 dirty(比如置为 1)。

注意几个关键点:

  • 写屏障只在**老年代对象引用新生代对象**时触发,反向不触发
  • HotSpot 默认使用的是「后写屏障」(Post-write barrier),开销极小,现代 CPU 流水线可很好掩盖
  • 如果关闭写屏障(如用 -XX:-UseCondCardMark),卡表可能漏标,导致 Minor GC 漏掉存活对象,最终引发错误回收

卡表失效的典型场景:CMS 和 G1 的不同应对

卡表不是万能的。当发生并发修改(比如用户线程一边写、GC 线程一边扫描卡表),就可能出现“漏标”。CMS 和 G1 的处理方式直接体现了卡表的局限性:

CMS 使用增量更新(Incremental Update):一旦发现老年代对象新增了对新生代的引用,就把它重新标灰,后续再扫——这会增加 Minor GC 的扫描工作量,但保证不漏。

阅读全文