HotSpot的算法细节实现

一、根节点枚举

我们可以通过可达性算法判断对象是否死亡,可达性算法的思路是GC Root集合的引用,而固定可作为GC Roots的节点主要在全局性的引用与执行上下文中,现在Java方法区的大小就常有数百上千兆,里面的类、常量等更是恒河沙数,如果逐个检查以这里为起源的引用肯定要消耗不少时间。

注意:根节点枚举必须暂停用户线程

OoPMap

当用户线程停顿下来之后,其实并不需要一个不漏地检查完所有执行上下文和全局的引用位置,一旦类加载动作完成,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,然后存入一组称为OopMap的数据结构中,在即时编译的过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用,这样收集器在扫描的时候就可以直接根据OopMap中记录的引用地址进行查找

二、安全点

上面介绍OopMap的时候说过,也会在特定的位置记录下栈里和寄存器里哪些位置是引用,这些特定的位置就是安全点,有了安全点的设点,也就决定了用户程序执行时并非在代码指令流的任意位置都能够停顿下来开始垃圾收集,而是强制要求必须执行到达安全点后才能够暂停。

安全点的位置

安全点的选定既不能太少以至于当收集器等待时间过长,也不能太过频繁以至于过分增大运行时的内存负荷。安全点的位置选取基本上是以“是否具有让程序长时间执行的特征”为标准进行选定的,因为程序长时间执行时更容易产生垃圾。“长时间执行”的最明显特征就是指令序列的复用,例如方法调用、循环跳转、异常跳转等都属于指令序列复用,所以只有具有这些功能的指令才会参数安全点

在垃圾收集发生时,如何让所有线程跑到最近安全点停下来?这里有两种方案:抢先式中断和主动式中断

抢先式中断

抢先式中断不需要线程的执行代码主动去配合,在垃圾收集发生时,系统首先把所有线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它跑到安全点上再重新中断

主动式中断

主动式中断的思想是当垃圾收集需要中断线程的时候,不直接对线程操作,设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志位真时就自己在最近的安全点上主动中断挂起。轮询的地方和安全点是重合的,另外还要加上所有创建对象和其他需要在Java堆上分配内存的地方,这是为了检查是否即将要发生垃圾收集,避免没有足够内存分配新对象

由于轮询操作在代码中会频繁出现,为了让它足够高效HotSpot使用内存保护陷阱的方式,把轮询操作精简至只有一条汇编指令的程度

三、安全区域

安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入垃圾收集过程的安全点,但程序“不执行”的时候(用户线程处于Sleep状态或者Blocked状态),线程就不能走到安全点去中断挂起自己,这时就必须引入安全区域来解决。

安全区域能够确保在某一段代码片段之中,引用关系不会发生变化,在这个区域中任意地方开始垃圾收集都是安全的,可以把安全区域看作被扩展拉伸了的安全点。

当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,当这段时间里虚拟机要发起垃圾收集时就不需要去管这些已声明自己在安全区域的线程了。当线程要离开安全区域时,它会检查虚拟机是否已经完成了根节点枚举(或者在垃圾收集过程中还有其他线程需要暂停)。如果完成了,线程继续执行,否则一直等待直到收到可以离开安全区域的信号

四、记忆集与卡表

记忆集

记忆集可以使对象跨代引用时避免把整个老年代加进GC Roots的扫描范围。是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构,这里选择更为粗犷的记录粒度来节省记忆集的存储和维护成本,下面是可供选择的记录精度

  • 字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,这个精度决定了机器访问处理内存地址的指针长度),该字包含跨代指针
  • 对象精度:每个记录精确到一个对象,该对象有字段含有跨代指针
  • 卡精度:每个记录精确到一块内存区域,该区域内对象含有跨代指针

这里的“卡精度”指的是用一种称为“卡表”的方式去实现记忆集,这也是目前最常用的一种记忆集实现形式

卡表

前面提到记忆集是一种“抽象”的数据结构,而卡表就是记忆集的一种具体实现,卡表最简单的形式可以只是一个字节数组

字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,内存卡称为“卡页”(Card Page),下图为卡表与卡页的对应图:

在这里插入图片描述
一个卡页的内存中通常包含不止一个对象,只要卡页内有一个或多个对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为一,称这个元素为变脏(Dirty),没有则标识为0。在垃圾回收时,只要筛选出卡表中变脏的元素,就可以知道哪些卡页中包含跨代指针,然后加入GC Roots中一并扫描。

使用Byte数组是因为现代计算机硬件都是最小按字节寻址的,没有直接存储一个bit的指令

五、写屏障

当有其他分代区域中的对象引用了本区域对象时,对应的卡表元素就会变脏,变脏的时间点原则上应该发送在应用类型字段赋值的那一刻,但问题是如何在对象赋值的那一刻去更新维护卡表呢?

假如是解释执行的场景,虚拟机负责每条字节码指令的执行,有充分的介入空间;但在解释执行的场景中,代码经过即时编译后,已经是纯粹的机器指令流了,所以必须找到一个在机器码层面的手段,把维护卡表的动作放到每一个赋值操作中,这时就需要写屏障技术。

写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形通知,表示赋值的前后都在写屏障的覆盖范畴内,供程序执行额外的动作。比如变脏动作。

写前屏障:赋值前的部分的写屏障
写后屏障:赋值后的部分的写屏障

六、伪共享问题

伪共享是处理并发底层细节时一种经常需要考虑的问题,比如:在高并发场景下的卡表。

现代中央处理器的缓存系统中是以‘缓存行’为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰好共享同一个缓存行,就会彼此影响(写回、无效化或者同步)而导致性能降低,这就是伪共享问题。

解决方案

对写屏障添加条件判断,先检查卡表标记,只有当该卡表元素未被标记过时才将其标记为变脏

七、并发的可达性分析

产生问题

当前主流编程语言的垃圾收集器基本上都是以可达性算法来判定对象是否存活,理论上要求全过程都基于一个能保障一致性的快照中才能够进行分析,这意味着必须全程冻结用户线程的运行。这就产生了一个问题:当GC Root往下遍历对象图时,这一步骤的停顿时间就会与Java堆容量直接成正比关系:堆越大,存储的对象越多,对象图的结构越复杂,标记更多对象产生的停顿时间就更长了。

“标记”阶段是所有追踪式垃圾收集算法的共同特征,如果这个问题发生在这个阶段,其影响就会波及到几乎所有的垃圾收集器,同理,如果能够消减这部分的停顿时间,那收益也将是系统性的

解释问题

想解决或者降低用户线程的停顿,就要先搞清楚为什么必须在一个能保障一致性的快照上才能进行对象图的遍历?这里我们引入三色标记作为工具来辅助推导,来解释这个问题。把遍历对象图过程中遇到的对象,按照“是否访问过”这个条件标记成以下三种颜色:

  • 白色:表示对象尚未被垃圾收集器访问过,在可达性刚开始的阶段,所有对象都是白色的,但如果在分析结束的阶段仍然是白色的,即表示不可达
  • 黑色:表示对象已经被垃圾收集访问过,且这个对象的所有引用都已经扫描过,所以是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象
  • 表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过

关于可达性分析的扫描过程,可以把它看作对象图上一股以灰色会波峰的波纹从黑向白推进的过程,如果此时用户线程是冻结的,只有收集器线程在工作,那就不会出现任何问题。但如果用户线程与收集器并发工作,就会产生两种后果,一种是把原本消亡的对象错误标记为存活。另一种是把原本存活的对象错误标记为已灭亡,这后果是非常致命的,程序肯定会因此发生错误。下图表示致命错误是如何产生的:

在这里插入图片描述
产生“对象消失”的问题,即原本应该是黑色的对象被误标为白色:

  • 赋值器插入了一条或多条从黑色到白色对象的新引用
  • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用

因此,解决对象消失问题,只需破坏这两个条件中的任意一个即可,由此产生了两种解决方案

解决方案

  • 增量更新
  • 原始快照

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值