而且 gc 过程中新分配的对象也都认为是活的。每个 region 会维持 TAMS (top at mark start)指针,分别是 prevTAMS 和 nextTAMS 分别标记两次并发标记开始时候 Top 指针的位置。
Top 指针就是 region 中最新分配对象的位置,所以 nextTAMS 和 Top 之间区域的对象都是新分配的对象都认为其是存活的即可。
而利用增量更新的 cms 在 remark 阶段需要重新所有线程栈和整个年轻代,因为等于之前的根有新增,所以需要重新扫描过,如果年轻代的对象很多的话会比较耗时。
要注意这阶段是 STW 的,很关键,所以 CMS 也提供了一个 CMSScavengeBeforeRemark 参数,来强制 remark 阶段之前来一次 YGC。
而 g1 通过 SATB 的话在最终标记阶段只需要扫描 SATB 记录的旧引用即可,从这方面来说会比 cms 快,但是也因为这样浮动垃圾会比 cms 多。
什么是 logging write barrier ?写屏障其实耗的是应用程序的性能,是在引用赋值的时候执行的逻辑,这个操作非常的频繁,因此就搞了个 logging write barrier。
把写屏障要执行的一些逻辑搬运到后台线程执行,来减轻对应用程序的影响。
在写屏障里只需要记录一个 log 信息到一个队列中,然后别的后台线程会从队列中取出信息来完成后续的操作,其实就是异步思想。
像 SATB write barrier ,每个 Java 线程有一个独立的、定长的 SATBMarkQueue,在写屏障里只把旧引用压入该队列中。满了之后会加到全局 SATBMarkQueueSet。
后台线程会扫描,如果超过一定阈值就会处理,开始 tracing。
在维护记忆集的写屏障也用了 logging write barrier 。
简单说下 G1 回收流程G1 从大局上看分为两大阶段,分别是并发标记和对象拷贝。
并发标记是基于 STAB 的,可以分为四大阶段:
1、初始标记(initial marking),这个阶段是 STW 的,扫描根集合,标记根直接可达的对象即可。在G1中标记对象是利用外部的bitmap来记录,而不是对象头。
2、并发阶段(concurrent marking),这个阶段和应用线程并发,从上一步标记的根直接可达对象开始进行 tracing,递归扫描所有可达对象。 STAB 也会在这个阶段记录着变更的引用。
3、最终标记(final marking), 这个阶段是 STW 的,处理 STAB 中的引用。
4、清理阶段(clenaup),这个阶段是 STW 的,根据标记的 bitmap 统计每个 region 存活对象的多少,如果有完全没存活的 region 则整体回收。
对象拷贝阶段(evacuation),这个阶段是 STW 的。
根据标记结果选择合适的 reigon 组成收集集合(collection set 即 CSet),然后将 CSet 存活对象拷贝到新 region 中。
G1 的瓶颈在于对象拷贝阶段,需要花较多的瓶颈来转移对象。
简单说下 cms 回收流程其实从之前问题的 CollectorState 枚举可以得知几个流程了。
1、初始标记(initial mark),这个阶段是 STW 的,扫描根集合,标记根直接可达的对象即可。
2、并发标记(Concurrent marking),这个阶段和应用线程并发,从上一步标记的根直接可达对象开始进行 tracing,递归扫描所有可达对象。
3、并发预清理(Concurrent precleaning),这个阶段和应用线程并发,就是想帮重新标记阶段先做点工作,扫描一下卡表脏的区域和新晋升到老年代的对象等,因为重新标记是 STW 的,所以分担一点。
4、可中断的预清理阶段(AbortablePreclean),这个和上一个阶段基本上一致,就是为了分担重新标记标记的工作。
5、重新标记(remark),这个阶段是 STW 的,因为并发阶段引用关系会发生变化,所以要重新遍历一遍新生代对象、Gc Roots、卡表等,来修正标记。
6、并发清理(Concurrent sweeping),这个阶段和应用线程并发,用于清理垃圾。
7、并发重置(Concurrent reset),这个阶段和应用线程并发,重置 cms 内部状态。
cms 的瓶颈就在于重新标记阶段,需要较长花费时间来进行重新扫描。
cms 写屏障又是维护卡表,又得维护增量更新?卡表其实只有一份,又得用来支持 YGC 又得支持 CMS 并发时的增量更新肯定是不够的。
每次 YGC 都会扫描重置卡表,这样增量更新的记录就被清理了。
所以还搞了个 mod-union table,在并发标记时,如果发生 YGC 需要重置卡表的记录时,就会更新 mod-union table 对应的位置。
这样 cms 重新标记阶段就能结合当时的卡表和 mod-union table 来处理增量更新,防止漏标对象了。
GC 调优的两大目标是啥?分别是最短暂停时间和吞吐量。
最短暂停时间:因为 GC 会 STW 暂停所有应用线程,这时候对于用户而言就等于卡顿了,因此对于时延敏感的应用来说减少 STW 的时间是关键。