OopMap(ordinary object pointer map)
记录时机
- JIT编译时在特定的位置(安全点/安全区)记录下OopMap,记录了执行到该方法的某条指令的时候,栈上和寄存器里哪些位置是引用
- 类加载动作完成时,HotSpot就会计算出对象内什么偏移量上是什么类型的数据
设计目标
- 加速GCRoot遍历,避免栈扫描
- 准确式GC
Remember Set
跨代/跨区引用关系记录
实现方式
- 稀疏表,字典=hash table(底层使用数组实现),key是region index,value是card数组,详细记录跨区引用的关系
- 细粒度表,数组=array(PerRegionTable),每个数组元素指向引用者分区中512字节内存块对本分区的引用情况。详细记录引用者分区内所有对本分区的每个引用关系。当稀疏表指定region的card数量超过阈值时,则在细粒度PRT中创建一个对应的PerRegionTable对象,其包含一个C heap位图,每一位对应一个card
- 粗粒度表,位图=bitmap,每个位代表一个分区,表示该分区对本分区存在引用。当细粒度PRT size超过阈值时,则退化为分区位图,每一位表示对应分区有引用到当前分区
记录范围
G1
- 分区内部引用(region):不需要记录RSet
- 新生代引用新生代:不需要记录RSet
- 新生代引用老生代:不需要记录RSet,Young GC(YGC)无需知道该引用关系。MixGC时G1会先进行YGC,然后使用YGC后的Survivor分区作为根(“借道”),因此也无需知道该引用关系。Full GC更无需知道该层关系
- 老生代引用老生代:需要记录RSet
- 老生代引用新生代:需要记录RSet
引用关系记录
通常有两种方法记录引用关系:Point In/Point Out
假设有引用关系如下:ObjB.feild= ObjC
- point out:在对象B的RSet中记录对象C的地址。记录简单,如果判断一个对象是有用/无用,需要遍历RSet,才能确定没有任何对象引用某个对象
- point in:在对象C的RSet中记录对象B的地址。记录复杂,如果判断一个对象是有用/无用,不需要遍历RSet
G1采用的是point in方法
SATB(Snapshot At The Beginning)
G1为并发标记引入SATB算法。
SATB 的作用
SATB 的核心思想是:在并发标记开始的那一刻,假设对象图就“定格”了一个快照,GC 要保证至少把这张快照里的存活对象都标记到。
实现方法:
-
当应用线程在并发标记阶段 写屏障(write barrier) 修改引用时,比如
oldRef.field = newObj
:- GC 会把 原来的引用(oldRef 指向的对象) 记录下来,放入一个队列。
- 确保这些对象不会因为引用被覆盖而丢失。
-
这样就保证了 不会漏标:即使对象在标记过程中“断开引用”,它仍然会被标记到。
SATB 是一种 保证并发标记正确性的增量快照技术。
该算法有个前提是堆的对象内存分配是连续。个人认为还有一个前提是使用了标记-整理算法,不存在内存碎片,新对象不会被分配至碎片内存中
- Bottom/Pre/Next/Top指针,用于工作内存区域划分
- Bottom:指向堆内存起始位置
- Pre TAMS(Top-At-Mark-Start):指向上一次并发标记处理后的内存地址终点
- Next TAMS(Top-At-Mark-Start):指向并发标记开始之前堆内存已分配成功的地址终点
- Top:指向并发标记开始之后当前堆内存分配成功的地址终点
- NextBitmap/PreBitmap位图,用于记录标记状态。记录PreBitmap的好处是如果标记失败,不会丢失上一次内存标记状况(即Pre指针之前的内存标记状况)
消失的对象问题(Lost Object Problem)
黑色:已经扫描过对象所有的引用
灰色:扫描中,还未完成所有引用的扫描
白色:未扫描或垃圾对象(在完成所有扫描标记之后,依然是白色,则说明是可以回收的垃圾对象)
问题描述
标记过程中修改了引用关系,B.C=null,A.D=D,此时如果不做特殊处理的话,那么D会被误杀,因为A已经为黑色标记完成,不会再尝试去标记它的子节点D
解决方法
问题出现的前提条件必须同时满足下面两个条件
- 新增黑色节点对白色节点的引用
- 删除灰色节点对白色节点的直接/间接引用
CMS解决方法是针对条件1:新增黑色节点对白色节点引用时会将新引用关系的引用者(黑色节点)标记为灰色
G1/ZGC解决方法是针对条件2:删除灰色节点对白色节点引用时会将老引用关系的被引用者标记为灰色