2014-12-11
#1
LeafInWind 写道
Incremental update关注的是第一个条件的打破,即引用关系的插入。Incremental update利用write barrier将所有新插入的引用关系都记录下来,最后以这些引用关系的src为根STW地重新扫描一遍即避免了漏标问题。
Incremental update的write barrier会拦截所有新插入的引用关系,并且按需要记录新的引用关系。常见实现会判断例如:a.foo = b 那么a是否是黑色对象而b是否是白色对象。也有完全不做过滤的变种。CMS具体使用的write barrier是无条件的,跟HotSpot VM除G1外的其它GC的card marking基本一样。
LeafInWind 写道
SATB关注的是第二个条件的打破,即引用关系的删除。SATB利用pre write barrier将所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根STW地重新扫描一遍即可避免漏标问题。
对的。
LeafInWind 写道
一个疑问就是http://hllvm.group.iteye.com/group/topic/44381?page=2中,
R大 写道
CMS的remark需要重新扫描mod-union table里的dirty card外加整个根集合
为什么还要“外加”扫描“整个根集合”。R大这里所说的根集合是指stack、register、global object这样的根吗,还是另有所指??在CMS remark的上下文里,根集合指stack、register、globals还有整个young gen。
要注意:CMS是一个old gen collector(不是whole heap collector)。既然只收集old gen,它必须把当前处于非收集区域的young gen算作是root。这跟一般的young GC时要把old gen的remembered set部分算作root的道理一样,只不过HotSpot没有用card table来记录young -> old引用(注),所以就干脆扫描整个young gen作为root。
在CMS initial mark的上下文里,根集合并不包括young gen而是只有stack、register、globals这些常规的。这是因为在接下来的CMS concurrent mark阶段CMS会顺着初始的根集合把young gen里的活对象都遍历了。所以从CMS initial mark + concurrent mark结合在一起的角度看,young gen仍然是根集合的一部分(因为被扫描但不被收集)。
但既然initial mark + concurrent mark已经扫过了young gen为啥还要再在remark时再扫?这就是因为CMS使用的incremental update write barrier是一种“grey mutator”做法。这在之前G1帖的回复里提到了。
如果把mutator也看作一个虚构的对象,那么它也应该有黑灰白的颜色划分。
所谓black mutator做法就是说mutator一旦被初始标记之后,到并发标记结束之前都不可以接触到白对象的指针,或者要确保接触到的白对象都被grey-protected(破坏条件2);
所谓grey mutator则正好相反,在mutator被初始标记之后,到并发标记结束之前还可以继续接触白对象的指针,只要在标记结束前重新扫描一次完整的根集合即可。。
Incremental update write barrier都是grey mutator做法;SATB write barrier则是black mutator做法。
CMS remark阶段做的就是为了确保grey mutator正确性而重新扫描根集合,同时也要把card table和mod-union table记录下的在old gen里发生了变化的引用也重新扫描一遍。
==============================
前面说了CMS的write barrier非常简单,只是在card table记录一下改变的引用的出发端对应的card。那mod-union table是啥?
其实很简单:card table只有一份,既要用来支持young GC又要用来支持CMS。每次young GC过程中都涉及重置和重新扫描card table,这样是满足了young GC的需求,但却破坏了CMS的需求——CMS需要的信息可能被young GC给重置掉了。
为了避免丢失信息,就在card table之外另外加了一个bitmap叫做mod-union table。在CMS concurrent marking正在运行的过程中,每当发生一次young GC,当young
GC要重置card table里的某个记录时,就会更新mod-union table对应的bit。
这样,最后到CMS remark的时候,当时的card table外加mod-union table就足以记录在并发标记过程中old gen发生的所有引用变化了。
==============================
注:实际上HotSpot VM一般用的post-write barrier非常简单,就是无条件的记录下发生过引用关系变化的card:
void post_write_barrier(oop* field, oop val) {
jbyte* card_ptr = card_for(field);
*card_ptr = dirty_card;
} 这里既不关心field所在的分代,也不关心val的值,所以其实只要有引用改变,其对应的card都会被记录。也就是说这个card table记录的不只是old -> young引用,而是所有发生了变化的引用的出发端,无论在old还是young。
但是HotSpot VM只使用了old gen部分的card table,也就是说只关心old -> ?的引用。这是因为一般认为young gen的引用变化率(mutation rate)非常高,其对应的card table部分可能大部分都是dirty的,要把young gen当作root的时候与其扫描card table还不如直接扫描整个young gen。
==============================
CMS论文里的原始描述:
引用
- Initial marking pause. Suspend all mutators and record all objects directly reachable from the roots (globals, stacks, registers) of the system.
- Concurrent marking phase. Resume mutator operation. At the same time, initiate a concurrent marking phase, which marks a transitive closure of reachable objects. This closure is not guaranteed to contain all objects reachable at the end of marking, since concurrent updates of reference fields by the mutator may have prevented the marking phase from reaching some live objects. To deal with this complication, the algorithm also arranges to keep track of updates to reference fields in heap objects. This is the only interaction between the mutator and the collector.
- Final marking pause. Suspend the mutators once again, and complete the marking phase by marking from the roots, considering modified reference fields in marked objects as additional roots. Since such fields contain the only references that the concurrent marking phase may not have observed, this ensures that the final transitive closure includes all objects reachable at the start of the final marking phase. It may also include some objects that became unreachable after they were marked. These will be collected during the next garbage collection cycle.
- Concurrent sweeping phase. Resume the mutators once again, and sweep concurrently over the heap, deallocating unmarked objects. Care must be taken not to deallocate newly-allocated objects. This can be accomplished by allocating objects “live” (i.e., marked), at least during this phase.