一、根节点枚举
迄今为止,所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的。
现在可达性分析算法耗时 最长的查找引用链的过程已经可以做到与用户线程一起并发,但根节点枚举始终还 是必须在一个能保障一致性的快照中才得以进行——这里“一致性”的意思是整个枚举期间执行子系统 看起来就像被冻结在某个时间点上,不会出现分析过程中,根节点集合的对象引用关系还在不断变化 的情况,若这点不能满足的话,分析结果准确性也就无法保证。
在HotSpot 的解决方案里,是使用一组称为OopMap的数据结构来存放对象引用。
二、安全点
安全点定义
实际上HotSpot也的确没有为每条指令都生成OopMap,只是在“特定的位置”记录 了这些信息,这些位置被称为安全点(Safepoint)。
哪些指令可以产生安全点
方法调用、循环跳转、异常跳转 等都属于指令序列复用,所以只有具有这些功能的指令才会产生安全点。
如何在垃圾收集发生时让所有线程都跑到最近的安全点
抢先式中断 (Preemptive Suspension)和主动式中断(Voluntary Suspension)
抢先式中断
抢先式中断不需要线程的执行代码 主动去配合,在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地 方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上。
注意 :现在几乎没有虚拟机实现采用抢先式中断来暂停线程响应GC事件。
主动式中断
主动式中断的思想是当垃圾收集需要中断线程的时候,不直接对线程操作,仅仅简单地设置一 个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最 近的安全点上主动中断挂起。
由于轮询操作在代码中会频繁出现,这要求它必须足够高效。HotSpot使用内存保护陷阱的方式, 把轮询操作精简至只有一条汇编指令的程度。
三、安全区域
安全点的问题
安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入垃圾收集 过程的安全点。但是,程序“不执行”的时候呢?所谓的程序不执行就是没有分配处理器时间,典型的 场景便是用户线程处于Sleep状态或者Blocked状态,这时候线程无法响应虚拟机的中断请求,不能再走 到安全的地方去中断挂起自己,虚拟机也显然不可能持续等待线程重新被激活分配处理器时间。
定义
安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任 意地方开始垃圾收集都是安全的。也可以看做被扩展拉伸了的安全点。
当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,那样当这段时 间里虚拟机要发起垃圾收集时就不必去管这些已声明自己在安全区域内的线程了。当线程要离开安全 区域时,它要检查虚拟机是否已经完成了根节点枚举(或者垃圾收集过程中其他需要暂停用户线程的 阶段),如果完成了,那线程就当作没事发生过,继续执行;否则它就必须一直等待,直到收到可以 离开安全区域的信号为止
四、记忆集和卡表
记忆集
定义
记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构
拓展
在垃圾 收集的场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在有指向了收集区域的指针 就可以了,并不需要了解这些跨代指针的全部细节。
卡表
定义
卡表就是记忆集的一种具体实现,它定义了记忆集的记录精度、与堆内存的映射关系等。
拓展
卡表最简单的形式可以只是一个字节数组,而HotSpot虚拟机确实也是这样做的。
卡页大小都是以2的N次幂的字节数,HotSpot中使用的卡页是2的9次幂,即512字节(地址右移9位,相当于用地址除以512)。
一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代 指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。
五、写屏障
在HotSpot虚拟机里是通过写屏障(Write Barrier)技术维护卡表状态的
写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切 面[2],在引用对象赋值时会产生一个环形(Around)通知。
如何避免伪共享问题
一种简单的解决方案是不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表元 素未被标记过时才将其标记为变脏,
在JDK 7之后,HotSpot虚拟机增加了一个新的参数-XX:+UseCondCardMark,用来决定是否开启 卡表更新的条件判断。开启会增加一次额外判断的开销,但能够避免伪共享问题
六、并发的可达性分析
堆越大,存储的对象越多,对 象图结构越复杂,要标记更多对象而产生的停顿时间自然就更长。
为什么必须在一个能保障一致性的快照上才能进行对象图的遍历
为了能解释清楚这个问题,引入三色标记(Tri-color Marking) 来解决
三色标记
白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是 白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代 表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对 象不可能直接(不经过灰色对象)指向某个白色对象。
·灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。
产生“对象消失”问题的原因
当且仅当以下两个条件同时满足时,会产生“对象消失”的问 题,即原本应该是黑色的对象被误标为白色: ·
赋值器插入了一条或多条从黑色对象到白色对象的新引用; ·
赋值器删除了全部从灰色对象到该白色对象的直接或间接引用
“对象消失”问题的解决方案
增量更新和原始快照
增量更新
增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新 插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫 描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象 了。
原始快照
原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删 除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描 一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来 进行搜索。