8. 并发标记清除法的难点是什么?
在没有用户态代码并发修改三色抽象
的情况下,回收可以正常结束。但是并发回收的根本问题在于,用户态代码在回收过程中会并发地更新对象图,从而造成赋值器和回收器可能对对象图的结构产生不同的认知。这时以一个固定的三色波面作为回收过程前进的边界则不再合理。
我们不妨考虑赋值器写操作的例子:
时序 | 回收器 | 赋值器 | 说明 |
---|---|---|---|
1 | shade(A, gray) | 回收器:根对象的子节点着色为灰色对象 | |
2 | shade(C, black) | 回收器:当所有子节点着色为灰色后,将节点着为黑色 | |
3 | C.ref3 = C.ref2.ref1 | 赋值器:并发的修改了 C 的子节点 | |
4 | A.ref1 = nil | 赋值器:并发的修改了 A 的子节点 | |
5 | shade(A.ref1, gray) | 回收器:进一步灰色对象的子节点并着色为灰色对象,这时由于 A.ref1 为 nil ,什么事情也没有发生 |
|
6 | shade(A, black) | 回收器:由于所有子节点均已标记,回收器也不会重新扫描已经被标记为黑色的对象,此时 A 被着色为黑色,scan(A) 什么也不会发生,进而 B 在此次回收过程中永远不会被标记为黑色,进而错误地被回收。 |
- 初始状态:假设某个黑色对象 C 指向某个灰色对象 A ,而 A 指向白色对象 B;
C.ref3 = C.ref2.ref1
:赋值器并发地将黑色对象 C 指向(ref3)了白色对象 B;A.ref1 = nil
:移除灰色对象 A 对白色对象 B 的引用(ref2);- 最终状态:在继续扫描的过程中,白色对象 B 永远不会被标记为黑色对象了(回收器不会重新扫描黑色对象),进而对象 B 被错误地回收。
总而言之,并发标记清除中面临的一个根本问题就是如何保证标记与清除过程的正确性。