文章目录
- 七种典型的垃圾回收器
- 经典收集器与垃圾分代之间的关系
- 如何查看默认的垃圾回收器
- Serial回收器
- ParNew回收器
- Parallel Scavenge回收器
- CMS回收器
- G1回收器
- 总结
- ZGC
- 垃圾收集器的组合关系
七种典型的垃圾回收器
串行回收器:Serial、Serial Old
并行回收器:ParNew、Parallel Scavenge、Parallel Old
并发回收器:CMS、G1
经典收集器与垃圾分代之间的关系
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:Serial Old、Parallel Old、CMS
整堆收集器:G1
如何查看默认的垃圾回收器
查看命令行相关参数(包括使用的垃圾收集器):
-XX:+PrintCommandLineFlags
使用命令行指令:
jinfo -flag 相关垃圾收集器参数 进程ID
Serial回收器
串行回收,JDK1.3之前回收新生代唯一的选择。Serial
收集器作为Hotspot
中Client
模式下的默认新生代垃圾收集器。
该收集器采用复制算法、串行回收和STW
机制的方式执行内存回收。
Serial Old
也采用了串行回收和STW
机制,内存回收算法是标记-压缩算法。是运行在Client
模式下的默认的老年代的垃圾回收器。
Serial Old
在Server
模式下主要有两个用途:
- 与新生代的
Parallel Scavenge
配合使用 - 作为老年代
CMS
收集器的后备垃圾收集方案
该收集器是一个单线程收集器,它只会使用一个CPU或一条收集线程去完成垃圾收集工作,并且在他进行垃圾回收的时候,必须暂停其他所有的工作线程,直到它收集结束。
优势:
简单而高效,对于限定单个CPU的环境来说,该收集器由于没有线程交互的开销,专心做垃圾收集自然获得最高的单线程收集效率。
在Hotspot
中,使用-XX:+UseSerialGC
参数可以指定年轻代和老年代都使用串行收集器。
ParNew回收器
并行回收,ParNew
收集器除了采用并行回收的方式执行内存回收外,与Serial
垃圾收集器之间几乎没有任何区别。在年轻代中同样是采用复制算法、STW
机制。该垃圾收集器是很多JVM运行在Server
模式下新生代的默认垃圾收集器。
对于新生代,回收次数频繁,使用并行方式高效。
对于老年代,回收次数少,使用串行方式节省资源(CPU并行需要切换线程,串行可以省去切换线程的资源)。
Parallel Scavenge回收器
吞吐量优先,该收集器同样也采用了复制算法、并行回收和STW
机制。
该收集器的目标是达到一个可控制的吞吐量,自适应调节策略也是该收集器与ParNew
的一个重要区别。
高吞吐量可以高效率的利用CPU的时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。常见在服务器环境中使用。Parallel
在JDK1.6时提供了用于执行老年代垃圾收集的Parallel Old
收集器,用来替代Serial Old
收集器。Parallel Old
采用了标记-压缩算法,是基于并行回收和STW
机制。
在Java8中,默认是此垃圾收集器。
自适应调节策略:
- 在这种模式下,年轻代的大小、
Eden
和Survivor
的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点。 - 在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标吞吐量和停顿时间,让虚拟机自己完成调优工作。
CMS回收器
低延迟,JDK1.5时期,Hotspot推出了一款适合在强交互应用中使用的垃圾收集器,是一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程同时工作。
CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。该收集器采用标记-清除算法,STW
机制。
CMS作为老年代的收集器,无法与新生代收集器Parallel Scavenge
(底层框架不同)配合工作,所以在JDK1.5中使用CMS作为老年代收集器的时候,新生代只能选择ParNew
或Serial
收集器中的一个。
CMS整个过程分为4个主要阶段:
初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段。
3. 初始标记阶段:在这个阶段中,程序中所有工作线程都将会因为STW
机制而出现短暂的暂停,这个阶段的主要任务是仅仅标记出GC Roots
能直接关联到的对象。一旦标记结束之后就会恢复之前被暂停的所有应用线程,由于直接关联的对象比较小,所以这里速度非常快。
4. 并发标记阶段:从GC Roots
的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要暂停用户线程,可以与垃圾收集线程一起并发运行。
5. 重新标记阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。(STW)
6. 并发清除阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间,由于不需要移动存活对象,所以这个阶段也是可以和用户线程同时并发的。(存在碎片问题)。
CMS优点:
- 并发收集
- 低延迟
CMS缺点:
- 会产生内存碎片
- CMS收集器对CPU资源非常的敏感
- CMS收集器无法处理浮动垃圾
G1回收器
区域分代化。是JDK9以后的默认垃圾回收器。G1的设定目标是在延迟可控的情况下获得尽可能高的吞吐量。G1是一个并行回收器,他把堆内存分割为很多不相关的区域(物理上不连续),使用不同的区域来表示Enden、幸存者0区、幸存者1区、老年代等。
该收集器有计划的避免在整个Java堆上进行全区域的垃圾收集,G1跟踪各个区域里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的区域。
G1是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。
优势:
-
并行与并发
并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力。此时用户线程STW.
并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况。 -
分代收集
它区分老年代和年轻代,它同时兼顾老年代和年轻代 -
空间整合
内存的回收是以Region
为单位的,Region
之间是复制算法,但整体上实际可看做是标记-压缩算法,这两种算法都可以避免内存碎片。这话特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。 -
可预测的停顿时间模型(软实时)
由于分区的原因,G1可以值选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。
缺点:
- 相较于CMS,G1无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS要高。(需占用额外的内存空间)
- 在小内存应用上CMS的表现大概率优于G1,G1在大内存应用上发挥其优势。
G1垃圾收集器还增加了一个新的内存区域,叫Humongous
内存区域,主要用于存储大对象,如果超过了1.5个region
区域,就放到H区。
设置H区的原因:
对于堆中的大对象,默认直接被分配到老年代,但是如果他是一个短期存在的大对象,就会对垃圾收集器造成负面影响(内存泄露),为了解决这个问题,G1划分了一个Humongous
区,它用来专门存放大对象。如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储,为了能找到连续的H区,有时候不得不启动Full GC
。G1的大多数行为都把H区作为老年代的一部分来看待。
G1垃圾回收器的回收过程:
应用程序分配内存,当年轻代的Eden
区用尽时开始年轻代的回收过程:G1的年轻代收集阶段是一个并行的独占式收集器。在年轻代回收期,G1 GC暂停所有应用程序的线程,启动多线程执行年轻代的回收。然后从年轻代区间移动存活对象到Survivor
区间或者老年区间,也有可能是两个区间都会涉及。
当堆内存使用达到一定值(默认为45%)时,开始老年代并发标记过程。
标记完成马上开始混合回收过程。对于一个混合回收期,G1 GC从老年区间移动存活对象到空闲区域,这些空闲区域也就成为了老年代的一部分。和年轻代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region
就可以了,同时,这个老年代Region
和年轻代一起被回收。
- 年轻代GC:
JVM启动时,G1先准备好Eden
区,程序在运行过程中不断创建对象到Eden
区,当Eden
空间耗尽时,G1会启动一次年轻代垃圾回收过程。年轻代垃圾回收只会回收Eden
和Survivor
区。YGC
时,首先G1
停止应用程序的执行,G1创建回收集,回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包括年轻代Eden
区和Survivor
区所有的内存片段。
具体过程:
(1)第一阶段:扫描根
根指的是static
变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同RSet
(记忆集)记录的外部引用作为扫描存活对象的入口。
(2)第二阶段:更新RSet
处理dirty card queue
中的card
,更新RSet
。此阶段完成后,RSet
可以准确的反应老年代对所在的内存分段中的对象的引用。
(3)第三阶段:处理RSet
识别被老年代对象指向的Eden
区中的对象,这些被指向的Eden
中的对象被认为是存活的对象。
(4)第四阶段:复制对象
此阶段,对象树被遍历,Eden
区内存段中存活的对象会被复制到Survivor
区中空的内存分段,Survivor
区内存段中存活的对象如果年龄值未达到阈值,则会加1,达到阈值就会被复制到Old
区空的内存分段。如果Survivor
区空间不够,Eden
空间的部分数据会直接晋升到老年代的空间。
(5)第五阶段:处理引用
处理Soft、Weak、Phantom、Final、JNI Weak
等引用。最终Eden
区空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。 - 老年代并发标记过程
(1)初始阶段标记
标记从根节点直接可达的对象。这个阶段是STW
的,并且会触发一次年轻代的GC。
(2)根区域扫描
G1 GC扫描Survivor
区中直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在young GC
之前完成。
(3)并发标记
在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC
中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
(4)再次标记
由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning(STAB)
。
(5)独占清理
计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域,为下阶段做铺垫,是STW的。这个阶段实际上不会去做垃圾的收集。
(6)并发清理阶段
是被并清理完全空闲的区域。 - 混合回收
当越来越多的对象晋升到老年代Old region
时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC
,除了回收整个Young Region
,还会回收一部分的Old Region
,这里需要注意,是部分老年代,而不是整个老年代。可以选择哪些Old Region
进行回收,从而可以对垃圾回收的耗时时间进行控制,也要注意的是Mixed GC
并不是Full GC
。
(1)并发标记结束之后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次被回收。
(2)混合回收的回收集包括八分之一的老年代内存分段,Eden
区内存分段,Survivor
区内存分段。混合回收的算法和年轻代回收的算法完全一样,只是回收集多了老年代的内存分段。
(3)由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段的比例越高,越先被回收。并且有一个阈值会决定内存分段是否被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间。
(4)混合回收并不一定进行8次,可以设置阈值-XX:G1HeapWastePercent
,默认值为10%。 - 如果需要,单线程、独占式、高强度的
Full GC
还是继续存在的,他针对GC的评估失败提供了一种失败的保护机制,即强力回收。导致full GC
的原因有两个:
(1)Evacuation
的时候没有足够的to-space
来存放晋升的对象
(2)并发处理过程完成之前空间耗尽。
总结
垃圾收集器 | 分类 | 作用位置 | 使用算法 | 特点 | 适用场景 |
---|---|---|---|---|---|
Serial | 串行运行 | 新生代 | 复制算法 | 响应速度优先 | 适用于单CPU环境下的client场景 |
ParNew | 并行运行 | 新生代 | 复制算法 | 响应速度优先 | 多CPU环境Server模式下与CMS配合使用 |
Parallel | 并行运行 | 新生代 | 复制算法 | 吞吐量优先 | 适用于后台运算而不需要太多交互的场景 |
Serial Old | 串行运行 | 老年代 | 标记-压缩算法 | 响应速度优先 | 适用于单CPU环境下的client场景 |
Parallel Old | 并行运行 | 老年代 | 标记-压缩算法 | 吞吐量优先 | 适用于后台运算而不需要太多交互的场景 |
CMS | 并发运行 | 老年代 | 标记-清除算法 | 响应速度优先 | 适用于互联网或B/S业务 |
G1 | 并发、并行运行 | 新生代、老年代 | 标记-压缩、复制算法 | 响应速度优先 | 面向服务端应用 |
ZGC
在尽可能对吞吐量影响不大的情况下,实现在任意堆内存大小都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。
ZGC收集器是一款基于Region
内存布局的,不设分代的,使用了读屏障、染色体指针和内存多重映射等技术来实现的可并发的标记-压缩算法,以低延迟为首要目标的一款垃圾收集器。
工作过程分为四个阶段:
并发标记->并发预备重分配->并发重分配->并发重映射等。
ZGC几乎在所有的地方并发执行,除了初始标记的STW。
垃圾收集器的组合关系
说明:
- 其中
Serial Old
作为CMS
出现“Concurrent Mode Failure”
失败后的后备预案。 - 橘色虚线表示由于维护和兼容性测试的成本,在JDK 8时将
Serial+CMS、ParNew+Serial Old
这两个组合声明为废弃,并在JDK 9中完全取消了对这些组合的支持。 - 绿色虚线表示JDK14中,弃用
Parallel Scavenge
和SerialOld GC
的组合。 - JDK14中,删除了CMS垃圾回收器。