栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

三色标记算法和浮动垃圾

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

三色标记算法和浮动垃圾

三色标记 在并发标记的过程中 ,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。 这里我们引入“ 三色标记 ”来给大家解释下,把Gcroots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以 下三种颜色: 黑色 : 表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描 过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过 灰色对象) 指向某个白色对象。 灰色 : 表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。 白色 : 表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若 在分析结束的阶段, 仍然是白色的对象, 即代表不可达。

1 6 public class ThreeColorRemark { 7 8 public static void main ( String [] args ) { 9 A a = new A (); 10 // 开始做并发标记 11 D d = a . b . d ; // 1. 读 12 a . b . d = null ; // 2. 写 13 a . d = d ; // 3. 写 14 } 15 } 16 17 class A { 18 B b = new B (); 19 D d = null ; 20 } 21 22 class B { 23 C c = new C (); 24 D d = new D (); 25 } 26 27 class C { 28 } 29 30 class D { 31 } 多标-浮动垃圾 在并发标记过程中,如果由于方法运行结束导致部分局部变量(gcroot)被销毁,这个gcroot引用的对象之前又被扫描过 (被标记为非垃圾对象),那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存,被称之为“ 浮动 垃圾 ”。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除。 另外, 针对并发标记(还有并发清理)开始后产生的新对象,通常的做法是直接全部当成黑色 ,本轮不会进行清除。这部分 对象期间可能也会变为垃圾,这也算是浮动垃圾的一部分。 漏标-读写屏障 漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案: 增量更新( Incremental Update) 和原始快照( Snapshot At The Beginning,SATB) 。 增量更新 就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之 后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向 白色对象的引用之后, 它就变回灰色对象了 。 原始快照 就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑 色( 目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾 ) 以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过 写屏障 实现的。 写屏障 给某个对象的成员变量赋值时,其底层代码大概长这样: 1 5 void oop_field_store ( oop * field , oop new_value ) { 6 * field = new_value ; // 赋值操作 7 } 所谓的写屏障,其实就是指在赋值操作前后,加入一些处理(可以参考AOP的概念): 1 void oop_field_store ( oop * field , oop new_value ) { 2 pre_write_barrier ( field ); // 写屏障 ‐ 写前操作 3 * field = new_value ; 4 post_write_barrier ( field , value ); // 写屏障 ‐ 写后操作 5 } 写屏障实现SATB 当对象B的成员变量的引用发生变化时,比如引用消失( a.b.d = null),我们可以利用写屏障,将B 原来成员变量的引用 对象D记录下来: 1 void pre_write_barrier ( oop * field ) { 2 oop old_value = * field ; // 获取旧值 3 remark_set . add ( old_value ); // 记录原来的引用对象 4 } 写屏障实现增量更新 当对象A的成员变量的引用发生变化时,比如新增引用( a.d = d),我们可以利用写屏障,将A 新的成员变量引用 对象D 记录下来: 1 void post_write_barrier ( oop * field , oop new_value ) { 2 remark_set . add ( new_value ); // 记录新引用的对象 3 } 读屏障 1 oop oop_field_load ( oop * field ) { 2 pre_load_barrier ( field ); // 读屏障 ‐ 读取前操作 3 return * field ; 4 } 读屏障是直接针对第一步:D d = a.b.d,当读取成员变量时,一律记录下来: 1 void pre_load_barrier ( oop * field ) { 2 oop old_value = * field ; 3 remark_set . add ( old_value ); // 记录读取到的对象 4 } 现代追踪式(可达性分析)的垃圾回收器几乎都借鉴了三色标记的算法思想,尽管实现的方式不尽相同:比如白色/黑色 集合一般都不会出现(但是有其他体现颜色的地方)、灰色集合可以通过栈/队列/缓存日志等方式进行实现、遍历方式可 以是广度/深度遍历等等。 对于读写屏障,以Java HotSpot VM为例,其并发标记时对漏标的处理方案如下: CMS:写屏障 + 增量更新 G1,Shenandoah:写屏障 + SATB ZGC:读屏障 工程实现中,读写屏障还有其他功能,比如写屏障可以用于记录跨代/区引用的变化,读屏障可以用于支持移动对象的并 发执行等。功能之外,还有性能的考虑,所以对于选择哪种,每款垃圾回收器都有自己的想法。

注意:读屏障和写屏障并不是所有阶段都是有,只在并发标记阶段会有。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/276871.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号