Serial收集器
HotSpot虚拟机运行在客户端模式下的默认新生代收集器。
类型:单线程串行垃圾回收器
垃圾收集算法:复制算法
作用区域:新生代
特点:
1、只会用单个线程去完成垃圾收集工作,用户线程会STW,直到收集结束。
2、没有线程交互,专心做垃圾收集,获得最高的单线程收集效率。
ParNew收集器
类型:多线程并行垃圾回收器
垃圾收集算法:复制算法
作用区域:新生代
本质上就是Serial收集器的并行版本。
特点:
1、能与CMS收集器搭配使用。
Parallel Scavenge收集器
类型:多线程并行垃圾回收器
垃圾收集算法:复制算法
作用区域:新生代
特点:
1、是以吞吐量优先的垃圾回收器。(吞吐量=用户运行代码时间/用户运行代码时间+垃圾收集时间)
2、不能与CMS收集器搭配使用。
3、内置有一个 PS MarkSweep收集器,但是实现原理跟Serial实现基本一样。
为什么CMS只能和ParNew搭配使用,而不能和Parallel Scavenge搭配使用?
就性能来看,Parallel Scavenge它的性能会比ParNew的性能要好一些。
但是CMS只能和ParNew搭配使用,原因如下:
1、CMS的设计目标是低延迟,Parallel Scavenge的设计目标是高吞吐量。
2、Parallel Scavenge没有使用HotSpot的分代框架,而CMS使用了HotSpot的分代框架。(ParNew使用了HotSpot的分代框架)
Serial Old收集器
类型:单线程串型收集器
垃圾收集算法:标记-整理算法
作用区域:老年代
Parallel Old收集器
类型:多线程并行收集器
垃圾收集算法:标记-整理算法
作用区域:老年代
特点:
1、缓解了Parallel Scanvenge的尴尬局面。(在其出之前Parallel Scanvenge只能和Serial搭配使用)
CMS收集器
这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。
CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟)就越适合与用户交互的程序,良好的响应速度能提升用户体验。
类型:多线程并发收集器
垃圾收集算法:标记-清除算法
作用区域:老年代
工作原理
-
初始标记阶段:所有工作线程都会因为"Stop-the-World"机制而短暂暂停,这个阶段主要任务仅仅只是标记出GC Roots能直接关联到的对象。
-
并发标记阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要用户线程停顿。扫描完成后,这个过程可能会造成对象引用的改变。CMS使用了增量标记的技术,把改变的都引用发起者(黑色对象)存储起来。
-
重新标记阶段:主要是处理并发阶段中存储起来的黑色对象,在STW的前提下,重新以他们为根进行标记。
-
并发清除阶段:这个阶段清除掉标记阶段判断已经死亡的对象,释放内存空间。这个阶段可以与用户线程并发。
为什么采用标记-清除算法?
因为在清除过程中,GC线程是跟用户并发的,如果使用复制或标记-整理算法会造成对象内存地址的改变,那么会造成用户持有原先的引用而无法访问到对象的情况。
特点
1、触发CMS垃圾回收器时需要预留一定的空间来支持用户创建对象。
触发的阙值我们可以设置,但是在极端情况下,如果设置阙值过高,造成无法满足程序分配对象,那么虚拟机就会被迫启动Serial Old收集器来进行收集,这就会造成长时间的STW了。
2、会产生浮动垃圾
3、进行Full GC时,会进行内存碎片的整理
4、CMS对CPU资源非常敏感,在并发阶段时,虽然不会造成用户线程的STW,但是会因为占有CPU资源,而导致应用程序变慢,降低总吞吐量。
G1垃圾收集器
具有跨时代意义的垃圾收集器,设计思想:局部收集、基于Region的内存布局形式。
设计目的:回收垃圾最大化。
作用区域:整个堆
垃圾收集算法:从整体上来看是标记-整理算法。但从两个Region的角度来看,是标记-复制算法。
Region
G1保留了年轻代和老年代的概念,但新生代和老年代不再是固定的了,而是一系列不需要连续的动态集合。
其中,Homongous区域主要用来存放大对象。G1认为只要大小超过了一半的Region区域的对象就称为大对象。大多数情况下,G1把Homongous当成老年代看待。
设置H区域的原因
之前遇到大对象且年轻代Eden空间不够的话,那么会将大对象放到老年代,那么这个大对象得等到Old GC或者Full GC才能得以清理。
而设置H区之后:1、避免了内存碎片 2、提高了回收大对象的效率
工作原理
-
初始标记阶段:所有工作线程都会因为"Stop-the-World"机制而短暂暂停,这个阶段主要任务仅仅只是标记出GC Roots能直接关联到的对象。
-
并发标记阶段:从GC Root开始对堆中对象进行可达性分析,这个过程与用户线程并发执行。扫描完成后,这个过程中可能会存在对象引用的改变,G1使用原始快照(STAB)方式来避免出现漏标的情况。
-
重新标记阶段:主要处理并发标记过程中的STAB记录,在STW的前提下,以他们为根进行重新标记。
-
清除阶段:这个阶段需要用户线程STW。这个阶段清除掉标记阶段判断已经死亡的对象,释放内存空间。(使用的是复制算法)
问题
跨Region的引用问题:
当最开始我们使用分代模型的时候,只需要注意跨代引用,我们解决的思路是通过记忆集统计老年代到年轻代的引用即可。
而跨Region,我们就需要记录得跟复杂了。解题思路还是通过记忆集来记录跨Region引用,通过空间换时间的思路来避免全堆作为GC Root扫描。但是这个记忆集就需要每个Region都去维护自己独特的一个了,每个Region的记忆集不仅要记录我引用了谁,也要记录谁引用了我。
所以,记忆集造成了比较大的空间浪费。(10%~20%的堆空间浪费)
并发标记阶段如何避免漏标问题:
漏标:把存活的对象给他误删了。
G1使用了原始快照来解决漏标问题。当灰色对象要解开和白色对象的引用时,把白色对象记录下来。并在后面以他们为根,去扫描,让他们存活。(思路:就是以可能存在浮动垃圾的下,减少扫描的时间。)
Young GC & Mixed GC
G1通过构造一个可预测的时间模型,来在特定的时间内收获最高的垃圾。
那么在收集时,就不会只局限于年轻代了,当涉及多个代时,我们称为Mixed GC。
当只涉及年轻代时,我们还是称为Young GC。
ZGC垃圾收集器
概述
ZGC是JDK11推出的低延迟垃圾回收器。
设计目标:
1、停顿时间不超过10ms(低停顿)
2、停顿时间不会随着堆大小或者活跃对象的大小而增加。
适用场景:大内存低延迟服务。
前者GC的痛点
前者的GC在STW这块都不是特别友善,所以在一些低延迟服务造成了性能困扰。
CMS(ParNew)与G1停顿时间瓶颈
ParNew和G1使用的都是标记-复制算法。
瓶颈定位→复制阶段中的转移阶段中复制对象。
复制阶段中转移阶段是STW的,转移阶段需要分配新内存和复制对象的成员变量。其中内存分配比较快,但是在复制一些复杂对象的时候耗时会比较长。
为什么转移阶段不能和用户并发执行呢?
主要是无法解决转移过程中精确定位对象地址的问题。如果并发执行,那么会导致用户访问不到具体对象。
ZGC的原理
全并发
ZGC采用标记-复制算法。不过ZGC在标记、转移和重定位阶段几乎都是并发的。
ZGC只有三个STW阶段:初始标记,再标记,初始转移。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。
ZGC的技术
着色指针
着色指针是一种将信息存储在指针中的技术。
当应用程序创建对象时,首先在堆空间申请一个虚拟地址。同时会在M0、M1、Remapped地址空间分别申请一个虚拟地址,且三个虚拟地址对应同一个物理地址,但这三个虚拟地址在同一时间只有一个空间有效。
读屏障
Object o = obj.FieldA // 从堆中读取引用,需要加入屏障
<Load barrier>
Object p = o // 无需加入屏障,因为不是从堆中读取引用
o.dosomething() // 无需加入屏障,因为不是从堆中读取引用
int i = obj.FieldB //无需加入屏障,因为不是对象引用
ZGC中读屏障的代码作用:在对象标记和转移过程中,用于确定对象的引用是否满足条件,并做出对应的动作。
ZGC的并发处理过程
1、初始化:ZGC初始化之后,整个内存空间的地址视图被设置成Remapped。程序正常运行,在内存中分配对象,满足一定条件后垃圾回收启动,进入标记阶段。
2、并发标记阶段:第一次进入标记阶段时视图为M0,如果对象被GC标记线程或者应用线程访问过,那么就讲对象的地址视图从Remapped调整为M0。所以,在标记阶段之后,如果在M0就说明对象是活跃的,如果是在Remapped就说明对象是不活跃的。
3、并发转移阶段:标记结束后就进入转移阶段,此时地址视图再次被设置为Remapped,如果对象被GC标记线程或者应用程序访问过,那么会从M0调整为Remapped。
其实,在标记阶段存在两个地址视图M0和M1,上面的过程显示只用了一个地址视图。之所以设计成两个,是为了区别前一次标记和当前标记。也即,第二次进入并发标记阶段后,地址视图调整为M1,而非M0。
着色指针和读屏障技术不仅应用在并发转移阶段,还应用在并发标记阶段:将对象设置为已标记,传统的垃圾回收器需要进行一次内存访问,并将对象存活信息放在对象头中;而在ZGC中,只需要设置指针地址的第42~45位即可,并且因为是寄存器访问,所以速度比访问内存更快。