Excel不相邻列如何打印在一起-英雄云拓展知识分享
129
2023-11-07
【摘要】 本书摘自《深入理解 Java 虚拟机 JVM 高级特性与最佳实践(第3版)》一书中第3章,第4节,周志明著。
3.4.5 写屏障
我们已经解决了如何使用记忆集来缩减GC Roots扫描范围的问题,但还没有解决卡表 元素如何维护的问题,例如它们何时变脏、谁来把它们变脏等。
卡表元素何时变脏的答案是很明确的——有其他分代区域中对象引用了本区域对象时, 其对应的卡表元素就应该变脏,变脏时间点原则上应该发生在引用类型字段赋值的那一刻。 但问题是如何变脏,即如何在对象赋值的那一刻去更新维护卡表呢?假如是解释执行的字 节码,那相对好处理,虚拟机负责每条字节码指令的执行,有充分的介入空间;但在编译 执行的场景中呢?经过即时编译后的代码已经是纯粹的机器指令流了,这就必须找到一个 在机器码层面的手段,把维护卡表的动作放到每一个赋值操作之中。
在 HotSpot 虚拟机里是通过写屏障 (Write Barrier) 技术维护卡表状态的。先请读者注 意将这里提到的“写屏障”,以及后面在低延迟收集器中会提到的“读屏障”与解决并发乱 序执行问题中的“内存屏障”日区分开来,避免混淆。写屏障可以看作在虚拟机层面对“引 用类型字段赋值”这个动作的AOP 切面,在引用对象赋值时会产生 一 个环形 (Around) 通知,供程序执行额外的动作,也就是说赋值的前后都在写屏障的覆盖范畴内。在赋值前的 部分的写屏障叫作写前屏障 (Pre-Write Barrier), 在赋值后的则叫作写后屏障 (Post-Write Barrier) 。HotSpot 虚拟机的许多收集器中都有使用到写屏障,但直至G1 收集器出现之前,其 他收集器都只用到了写后屏障。下面这段代码清单3-6是一段更新卡表状态的简化逻辑:
代码清单3-6 写后屏障更新卡表
void oop_field_store(oop*field, oop new_value) {
/fede赋w值_a作lue;
//写后屏障,在这里完成卡表状态更新
post_write_barrier(field,new_value);
应用写屏障后,虚拟机就会为所有赋值操作生成相应的指令, 一旦收集器在写屏障中 增加了更新卡表操作,无论更新的是不是老年代对新生代对象的引用,每次只要对引用进 行更新,就会产生额外的开销,不过这个开销与Minor GC时扫描整个老年代的代价相比还 是低得多的。
除了写屏障的开销外,卡表在高并发场景下还面临着“伪共享” (False Sharing) 问题。 伪共享是处理并发底层细节时一种经常需要考虑的问题,现代中央处理器的缓存系统中是 以缓存行 (Cache Line) 为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰 好共享同一个缓存行,就会彼此影响(写回、无效化或者同步)而导致性能降低,这就是伪 共享问题。
假设处理器的缓存行大小为64字节,由于一个卡表元素占1个字节,64个卡表元素 将共享同一个缓存行。这64个卡表元素对应的卡页总的内存为32KB(64×512 字节),也 就是说如果不同线程更新的对象正好处于这32KB 的内存区域内,就会导致更新卡表时正 好写入同一个缓存行而影响性能。为了避免伪共享问题, 一种简单的解决方案是不采用无 条件的写屏障,而是先检查卡表标记,只有当该卡表元素未被标记过时才将其标记为变脏, 即将卡表更新的逻辑变为以下代码所示:if(CARD_TABLE [this address >>9] !=0)
CARD_TABLE [this address >>9] =0;
在 JDK7 之 后 ,HotSpot 虚拟机增加了一个新的参数-XX:+UseCondCardMark, 用来决 定是否开启卡表更新的条件判断。开启会增加一次额外判断的开销,但能够避免伪共享问 题,两者各有性能损耗,是否打开要根据应用实际运行情况来进行测试权衡。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们 18664393530@aliyun.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~