简要回顾各垃圾回收器的核心思想

人一老 就容易忘事


Serial 新生代单线程 复制到老年代

ParNew 新生代多线程 复制到老年代

Parallel Scavenge 新生代多线程 复制到老年代
关注点在 停顿时间 ,因此可以设置最大回收停顿时间,自动调节各代大小
和G1都没有用传统的GC框架,因此无法和老年代CMS共用


Serial Old 老年代单线程 标记整理

Parallel Old Parallel Scavenge的老年版,标记整理
配合Parallel Scavenge 可以只关注吞吐量

CMS 老年代并发 标记清除 1.4.1
初始标记,标记GC Root(初始标记的root并不包括年轻代,结合并发标记阶段看实际上年轻代也是GC Root的一部分)直达对象,阻塞
并发标记,从初始标记的结果开始爬可达性树,非阻塞
重新标记,并发标记完成后,修正在并发标记期间而变化的记录,阻塞
并发清除

缺点:
需要多核环境
并发清除时产生的垃圾只能留到下次,如果并发清除时内存不足只能Full GC
标记清除带来空间碎片,可以设置在Full GC前整理(默认开启)


G1 管理整个GC堆 但是依然有分代概念 6u14面世 7u4正式推出
准备替代CMS
G1暂停时需要复制对象,CMS暂停时只需要扫描对象,因此6G以下CMS不一定比G1差。推荐6G以上堆,可达到0.5s以下GC时间。

可用对象占用50%以上堆时
对象分配/变老速率显著变化时(CMS并发标记时,如果依然在高速分配内存,会导致很久的remark),
较长时间的GC/压缩发生时(0.5-1s以上)
建议切到G1,可以获得收益

设计理念:
停顿可预测:可以避免收集整个堆,而是跟踪每个Region中的垃圾大小和回收所需时间(也就是价值),优先回收价值高的Region,也是名字的来由
无碎片:内存分为等大的Region,整体为标记整理,实际是复制Region。
并行:扫描对象和复制对象分开运行,并且扫描对象的【初始标记】会借用复制对象的【年轻代复制】步骤。
空间换时间:使用Remembered Set记录老年代指向年轻代的指针,以及使用Collection Set记录需要收集的Region。

收集器部分

复制对象的两种运行模式:
Young gc:新生代满时运行,扫描所有年轻代的Region,找出半死的和全死的Region构成Collection Set。
并行的干掉死透的,并且把半死的Region中活对象复制到别的Region中。通过控制年轻代个数来控制开销

Mixed gc:扫描所有年轻代Region,和 全局并发标记 得出的高价值老年代Region构成Collection Set
。根据用户指定开销来调节价值范围,当mixed gc也清不出足够内存,老年代填满,就会用Searial Old 的核心代码 来Full GC。System.gc()也是Full GC,XX:+ExplicitGCInvokesConcurrent 会改为强行启动一次全局并发标记。

每个Region有对应的Remembered Set,只记录老年代到年轻代的 别的Region对本Region对象的引用,在写引用的时候阻塞,如果Region改变就把引用信息改到新Region的RSet中

全局并发标记的步骤:
初始标记,同CMS只扫描GC Root直达对象,压入扫描栈,阻塞

并发标记,弹栈,递归扫描对象图,还会扫描Write Barrier记录的引用(这些引用是每次改变引用时的老引用),非阻塞

重新标记,标记每个线程各自的Write Barrier(这个Barrier满了之后会丢到全局的去,而每个线程还有一个没满的Barrier),顺带处理弱引用,阻塞。只扫描SATB Buffer,而不是和CMS一样重新扫描整个GC Root,整个年轻代都会被扫描,可能会很慢

清理,类似于标记清理的清理阶段,但是不清理实际对象,而是计算每个Region的价值,根据用户要求的性能水平( -XX:MaxGCPauseMillis)优先清理价值高的,阻塞,所以如果要求的性能太高,反而容易造成垃圾堆积进而Full GC。

CSet永远包括年轻代,因此G1不维护年轻代出发的引用涉及的RSet更新

记录引用变化的部分

SATB Barrier
G1的设计思路是,一次GC开始时 活的对象认为是活的,并且记录为SATB(理解成快照) ,GC过程中新分配的都当做活对象。

GC中新分配的对象容易找,每个Region会记录两个TAMS指针(top-at-mark-start),此后的对象视为新分配的。

但是全局并发标记的并发标记过程中,由于和其他线程并行执行,会出现这种情况:

首先假设有一个对象的引用没有被标记过,记为A(其实就是白色状态)
前提1:给一个已经标记过、并且所有字段被标记完(黑色状态)的对象的字段赋值为A
前提2:并且所有 字段没有被标记完的引用(灰色状态) 到A的引用被删除了

这时候会出现A明明活着 却没有被标记到的情况,因此G1引入了两个WriteBarrier,在改变引用 前后 都会把老引用记下来,哪怕发生了前提2的事,A也会被标记下来。

Logging Barrier

G1为了尽量避免降低改变引用的性能,改变引用时 其实是将老引用加入一个队列,满了之后会被移到一个全局的SATB队列集合,然后换一个新的空队列。

而并发标记会定期检查全局SATB队列集合,当超过一定量时就把它们全部标记上,并且把它们压到标记栈上等后面进一步标记。

Remember Set

传统GC的
G1给每个Region维护一个Remembered Set,它记录别的Region指向本体的指针,并且这些指针分别在哪些Card Table的范围内。

维护Remembered Set的逻辑在改变引用 做,过滤掉从年轻代出发的引用 涉及的RSet维护。

维护RSet时也会采用Logging Barrier的设计思路,在全局队列集合超过一定量时,会取出若干个队列,并且更新RSet。


ZGC
并不是新货,而是Azul很久之前的Pauseless GC,而不如Zing VM的C4。
所有阶段都可以并发,很容易最大暂停控制在1ms内。

不标记对象,而是标记引用,访问引用时有Read Barrier,消耗读取引用时的性能,而干掉了STW。
Region有多种尺寸,根据对象大小分配
每次清理整个Region,因此没有RSet
支持NUMA,提高整体效率
没有分代(暂不完善,还在考虑是分代还是Thread Local GC作为前端),因此只有PGC的水平,遇到高速分配对象只能调大堆内存来喘息

初始扫描:只扫描全局变量和线程栈的指针,不扫描GC堆的指针
并发标记:递归对象图
移动对象:在移动过程中有forward table记录移动,并且活的对象移走后可以立即被释放,可以被其他扫描过程用来复制
修正指针:在修正时同时进行标记

简要回顾各垃圾回收器的核心思想

http://blog.mothership.top/posts/a63f2d9d.html

作者

Mother Ship

发布于

2018-11-22

更新于

2023-02-13

许可协议

评论