HotSpot算法的实现细节

HotSpot算法的实现细节

根节点枚举

HotSpot 是使用一组称为OopMap的数据结构来直接得到对象引用的。一旦类加载动作完成的时候, HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,存放到 OopMap 中,这样收集器在扫描时就可以直接得知这些信息了,并不需要真正一个不漏地从GC Roots开始查找

安全点

  1. OopMap可以快速准确地完成GC Roots枚举,但是有些问题无法解决:导致OopMap内容变化的指令非常多,如果为每一条字节码指令都生成对应的OopMap,那将会需要大量的额外存储空间。为了解决这个问题HotSpot并没有让每条字节码指令都生成OopMap,而是只在特定的位置生成OopMap,这个位置就被称为安全点。安全点机制保证了程序执行时,在不太长的时间内虚拟机就会进入垃圾回收状态。

  2. 安全点位置的选取标准:具有让程序长时间执行的特征。“长时间执行”的最明显特征就是指令序列的复用,例如方法调用、循环跳转、异常跳转 等都属于指令序列复用

  3. 在垃圾收集发生时让所有线程(这里其实不包括执行JNI调用的线程)都跑到最近的安全点,停顿下来,然后就可以安全的进行垃圾收集了

在垃圾收集发生时让所有线都跑到最近的安全点,然后停顿下来,这里有2】种中断方式

中断方式
抢先式中断

不需要线程的执行代码主动去配合,在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上为止。现在几乎没有虚拟机实现采用抢先式中断来暂停线程响应GC事件。

主动式中断

当垃圾收集需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。

安全区域

如果某个线程并没有在执行,例如某个线程正在Sleep或者Blocked,那这些线程是无法走到安全点的。对于这种情况,就必须引入安全区域来解决。安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,当这段时间里虚拟机要发起垃圾收集时就不必去管这些已声明自己在安全区域内的线程了。当线程要离开安全区域时,它要检查虚拟机是否已经完成了GC Roots枚举,如果完成了,那线程就当作没事发生过,继续执行;否则它就必须一直等待,直到收到可以离开安全区域的信号为止。

记忆集与卡表

记忆集

是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构,它的作用解决因对象跨代引用而把整个老年代加入GC Roots扫描范围这个问题的,它可以缩减GC Roots扫描范围。

3种精度实现方式
字长精度

每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或64位,这个精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针

对象精度

每个记录精确到一个对象,该对象里有字段含有跨代指针

卡精度

每个记录精确到一块内存区域,该区域内有对象含有跨代指针

卡表

是用“卡精度”方式的一种记忆集具体实现。

卡页

HotSpot默认的卡表只是一个字节数组,这个字节数组每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作卡页。只要卡页内有一个或更多对象的字段存在着跨代指针,那就把对应卡表数组元素的值标识置为为1,称为这个元素变脏(Dirty),否则标识为0。在垃圾收集时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,就会把它们加入GC Roots中一并扫描。

写屏障

在HotSpot虚拟机里是通过写屏障技术维护卡表状态的。写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形通知,供程序执行额外的动作。应用写屏障后,虚拟机就会为所有赋值操作生成相应的指令,在写屏障中会更新卡表状态。

存在的问题
  1. 每次只要对引用进行更新,就会产生额外的开销

  2. 存在伪共享问题:现代CPU的缓存系统中是以缓存行为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰好共享同一个缓存行,就会彼此影响而导致性能降低,这就是伪共享问题。

    1. 解决办法:不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表元素未被标记过时才将其标记为变脏

并发的可达性分析

三色标记算法
  1. 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。

  2. 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。

  3. 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能不经过灰色对象直接指向某个白色对象。

并发错误标记对象死亡

收集器在对象图上标记,同时用户线程在修改引用关系,导致把原本存活的对象错误标记为已死亡,也就是在收集线程和用户线程并发运行时会出现标记结果错误的问题。

产生原因

当且仅当以下两个条件同时满足时,会产生“对象消失”(被错误标记为死亡)的问题,即原本应该是黑色的对象被误标为白色

  1. 赋值器插入了一条或多条从黑色对象到白色对象的新引用

  2. 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用

解决方法--并发标记
增量更新

要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用关系记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。 CMS通过增量更新做并发标记

原始快照(STAB)

要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用关系记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描 一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。G1通过原始快照做并发标记

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值