算法实现细节
-
根节点枚举
必须stw(停止用户线程),必须在一个能保持一致性的快照中进行
具体实现有oopmap(避免全栈扫描,已经存储了哪些是reference)
-
安全点与安全区
只在特殊指令产生或更新oopmap,否则效率低
定义:能让程序长时间执行->字节码指令序列复用(方法调用,循环跳转,异常跳转)
为了保证所有线程都停在安全点,有两种方案;
-
抢先式中断
将所有线程中断然后将未到安全点的线程恢复直至到达安全点
-
主动式中断
设置标志位,各个线程不停轮询此标志位,发现中断标准为真时找到附近的安全点主动挂起
轮询操作hotspot采用内存保护陷阱的方式,需要暂停用户线程时,将指定内存页设置为不可读,线程执行到此指令就会自陷异常,在异常处理器中挂起等待。
安全区:如果线程睡眠或者block状态,无法响应虚拟机的中断请求,就不能走到安全点挂起自己,虚拟机又不能等待他激活,安全区应运而生,在某段代码片段中,引用关系不会发生变化,那么这段区域里进行垃圾收集就是安全的。
线程进入安全区域后会标识自己,虚拟机发起垃圾收集不必管此线程,此线程离开安全区域前要检查(stw状态是否已经结束),虚拟机收集结束后给线程离开的信号,线程离开安全区域。
-
-
记忆集与卡表
涉及到跨代或者跨区域的垃圾收集(如收集新生代则需要避免将整个老年代纳入GC roots扫描范围)
记忆集记录了从非收集区域到收集区域的指针集合。
因此不需要扫描整个老年代(非收集区域)
记录精度包括字长精度、对象精度,卡精度
hotspot使用卡表(卡精度)解决跨代引用扫描问题
卡表的每一个元素代表着其标识的内存区域中特定大小的内存块,被称为卡页,卡页中只要有一个(或者更多)存在跨代引用,卡表中对应的元素就会变脏,因此在跨代收集时只需扫描卡表,将包含跨代引用指针的内存区域纳入扫描即可。
-
写屏障(将卡表变脏的手段)
类似AOP里面的环绕通知,可以写前,也可以写后,直到G1前都只用到了写后。
写屏障有一定开销,还可能出现伪共享问题(高并发),如cpu缓存行64字节,如果卡表正好在同一行,对应区域发生变化就会改变卡表,彼此影响。
-
并发的可达性分析
三色标记,不怕有浮动垃圾,就怕把原本应该存活的对象判定为死亡
两个条件产生误判死刑:赋值器插入了黑对白的引用并且断开了灰对白的引用
增量更新(记录新增的黑对白引用,并在并发扫描后将有新增关系记录的黑色对象转变为灰色对象重新扫描,写后屏障)和原始快照(将灰色删除了的引用关系记录,以灰色对象为根重新扫描,写前屏障)的解决方案。
cms用增量更新,g1用了原始快照(可能产生更多的浮动垃圾,但是不用进行大规模扫描)