OopMap(ordinary object pointer map)
记录时机
- JIT编译时在特定的位置(安全点/安全区)记录下OopMap,记录了执行到该方法的某条指令的时候,栈上和寄存器里哪些位置是引用
- 类加载动作完成时,HotSpot就会计算出对象内什么偏移量上是什么类型的数据
设计目标
- 加速GCRoot遍历,避免栈扫描
- 准确式GC
Remember Set
跨代/跨区引用关系记录
实现方式
- 稀疏表,字典=hash table(底层使用数组实现)
- 细粒度表,数组=array,每个数组元素指向引用者分区中512字节内存块对本分区的引用情况。详细记录引用者分区内所有对本分区的每个引用关系
- 粗粒度表,位图=bitmap,每个位代表一个分区,表示该分区对本分区存在引用
记录范围
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:在对象C的RSet中记录对象B的地址。记录简单,如果判断一个对象是有用/无用,需要遍历RSet,才能确定没有任何对象引用某个对象
- point in:在对象B的RSet中记录对象C的地址。记录复杂,如果判断一个对象是有用/无用,不需要遍历RSet
G1采用的是point in方法
SATB(Snapshot At The Beginning)
G1为并发标记引入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:删除灰色节点对白色节点引用时会将老引用关系的被引用者标记为灰色