文章目录
1、GC分类与性能指标
1.1 垃圾回收器的分类
GC可以是动词(Garbage Collection)垃圾收集,也可以是名词(Garbage Collector)垃圾回收器,视具体使用环境决定,垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商、不同版本的JVM来实现。由于JDK的版本处于高速迭代过程中,因此Java发展至今已经衍生了众多的GC版本,从不同角度分析垃圾收集器,可以将GC分为不同的类型
:
1.1.1 按线程数分
按照线程数来分,可以分为串行垃圾回收器
和并行垃圾回收器
蓝色是用户线程,黄色是垃圾回收线程
串行回收
指的是同一时间段内只允许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直到垃圾收集工作结束。
并行收集
可以运用多个CPU同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然于串行回收一样,采用独占式,使用了“Stop-the-world”机制
- 在单CPU处理器或者较小的应用等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并发回收器和并发回收器。所以,
串行回收默认被应用在客户端的Client模式下的JVM中
- 在并发能力比较强的CPU上,并行回收器产生的停顿时间要短于串行回收器
1.1.2 按工作模式分
按照工作模式分,可以分为并发式垃圾回收器
和独占式垃圾回收器
并发式垃圾回收器
与应用程序线程交替工作,以尽可能减少应用程序的停顿时间独占式垃圾回收器
(Stop the world)一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。
1.1.3 按碎片处理方式分
按照碎片处理方式分,可分为压缩式垃圾回收器
和非压缩式垃圾回收器
- 压缩式垃圾回收器在回收完后,对存活对象进行压缩整理,消除回收后的碎片
- 非压缩式的垃圾回收器不进行这部操作
1.1.4 按工作的内存区间分
按工作的内存区间分,可分为年轻代垃圾回收器
和老年代垃圾回收器
1.2 评估GC的性能指标
-
吞吐量:运行用户代码的时间占总运行时间的比例
(总运行时间 = 程序运行时间 + 垃圾回收时间)
-
垃圾收集开销:吞吐量的补数,垃圾收集所用的时间与总运行时间的比例
-
暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
-
收集频率:相对于应用的执行,收集操作发生的频率
-
内存占用:Java堆区所占的内存大小
-
快速:一个对象从诞生到被回收所经历的时间
吞吐量、暂停时间、内存占用是最重要的三项指标,这三者构成一个“不可能三角”,任何一款垃圾收集器不可能全部满足
这三项中,延迟的重要性日益凸显,因为随着硬件发展,内存占用多些也越来越能容忍,硬件性能的提升也有助于降低垃圾收集器运行时对用户线程的影响,即能提高吞吐量,但内存的扩大,反而让进行GC时的延迟更长
所以在设计垃圾回收器时,现在主要考虑两点:吞吐量
和暂停时间
1.3 吞吐量vs暂停时间
-
吞吐量: CPU运行用户程序的时间占CPU总消耗时间的比值,吞吐量越高越好
吞吐量 = 运行用户程序时间 / (运行用户程序时间 + 垃圾收集时间)
- 高吞吐量的应用会让用户感觉程序一直在工作,在进行“生产”
-
延迟:进行GC的时间内,用户线程停止工作,这段时间就是延迟
- 延迟越低越好,低延迟的应用让用户有流畅的交互体验
高吞吐和低延迟是矛盾的,因为:
- 如果要求程序有高吞吐量,那么必须降低GC的频率,但是这会导致单次GC使用更多的时间,导致延迟更长
- 如果要求程序低延迟,就需要提高GC的频率,单次GC的时间变短,延迟降低,但是这会导致整个GC占用更多的时间,导致程序吞吐量降低。
现代回收器标准:在最大吞吐量优先的情况下,降低延迟时间
2、不同的垃圾回收器概述
2.1 垃圾回收器的发展史
有了JVM,就一定需要垃圾收集的机制,即Garbage Collection,对应的产品称为Garbage Collector
-
1991随JDK1.3.1一起发布的串行收集器
Serial GC
是第一款GC.ParNew
垃圾收集器是Serial收集器的多线程版本,一款并行回收器 -
2002
Parallel GC
和CMS GC
跟随JDK1.4.2发布 -
Parallel GC
在JDK1.6之后成为HotSpot VM的默认GC
-
2012,自JDK1.7u4版本中,
G1
可用 -
2017,
JDK9中G1成为默认的垃圾收集器
,以替换CMS -
2018,JDK10中
G1有了并行的完整垃圾回收
-
2018,JDK11,引入
Epsilon
回收器 同时引入ZGC
(可伸缩的低延迟垃圾回收器) 暂未实装 -
2019,JDK12中增强了G1 引入了
Shenandoah GC
(低延迟的GC) -
2019,JDK13
增强了ZGC
可以自动返回未用堆内存给操作系统 -
2020,JDK14
删除了CMS
扩展了ZGC在macOS和Windows上的应用
七款经典的垃圾收集器:
- 串行回收器:Serial、Serial Old
- 并行回收器:ParNew、Parallel Scavenge、Parallel Old
- 并发回收器:CMS、G1
2.2 经典回收器的回收区域
-
年轻代回收器: Serial, ParNew, Parallel Scavenge
-
老年代收集器: Serial Old, Parallel Old, CMS
-
整堆收集器: G1
2.3 垃圾收集器的组合关系
实线
: 表明二者可以搭配使用
红色虚线
: JDK8时被废弃,JDK9时被移除
绿色虚线
: JDK14时被弃用
青色虚线
: CMS在JDK14中被移除
为什么要有这么多的垃圾回收器,一个不够用吗?
因为Java的使用场景很多,移动端,服务器端等,所以需要对不同的场景,提供不同的垃圾回收器,以提高不同场景下垃圾回收的性能,没有完美的垃圾回收器,但有某种场景下最适合的垃圾收集器
// 查看默认的垃圾收集器
-XX:+PrintComandLineFlags
3、Serial回收器:串行回收
// 使用参数
-XX:+UseSerialGC
//指定JVM年轻代使用SerialGC,老年代使用Serial Old GC
//这两个串行收集器,只要指定一个,另外一个自动使用
-
Serial回收器是最基本,历史最长的垃圾回收器,在JDK1.3之前回收年轻代的唯一选择
-
Serial回收器作为HotSpot中Client模式下的默认年轻代垃圾回收器
-
Serial回收器
采用复制算法
,串行回收
和STW的机制
对年轻代
进行内存回收 -
除了年轻代外,Serial回收器还有专用于
回收老年代垃圾
的Serial Old
回收器, Serial Old使用标记-压缩算法
,串行回收
以及STW
机制对老年代
进行内存回收 -
Serial Old是
运行在Client模式
下默认
的老年代垃圾回收器 -
Serial Old在Server模式下主要有两个用途:
- 与新生代的Parallel Scavenge配合使用
- 作为老年代CMS收集器的后备垃圾收集方案
Serial/Serial Old是一个单线程的回收器,其单线程的意义不仅说明它只会使用一个线程去完成回收工作,更重要的是它在进行垃圾回收时,必须暂停其它所有工作线程(STW),直到回收结束
Serial的优缺点对比:
优点
简单且高效
(与其它单线程回收器相比),对于限定单个CPU的环境来说,Serial回收器由于没有线程交互的开销,专心做垃圾回收自然可以在单线程环境下达到最高的回收效率- 在内存不大(几十MB至一两百MB)的应用场景下,垃圾回收不频繁发生,使用串行回收器是可以接收的。
缺点
- 随着多核CPU的发展,串行的回收器已很少使用
- 对于交互性强的应用,这种回收器是不能接受的,回收时会导致用户线程完全停止
4、ParNew 回收器:并行回收
//使用ParNew
-XX:+UseParNewGC
// 表示年轻代使用并行收集器,不影响老年代
Serial GC
是年轻代中的单线程垃圾回收器,而ParNew
回收器是Serial回收器的多线程版本
,Par是Parallel的缩写,New表示只能处理年轻代
,ParNew除了使用多线程回收垃圾外,几乎和Serial没有区别
ParNew回收器采用复制算法,STW机制,并行的方式对年轻代进行垃圾回收,ParNew是很多JVM运行在Server模式下年轻代的默认垃圾回收器
ParNew回收器适合运行在多CPU的环境下,由于可以充分利用多CPU,多核心等物理硬件优势,可以更快速完成垃圾回收,提升程序的吞吐量,但是在单个CPU环境下,ParNew回收器不如Serial更高效,因为单CPU使用Serial 回收器不需要频繁的切换线程
除了Serial外,只有ParNew GC能和CMS收集器配合工作
5、Parallel 回收器:吞吐量优先
//使用Parallel
-XX:+UseAdaptiveSizePolicy
HotSpot的年轻代除了ParNew回收器是基于并行回收的以外,Parallel回收器也采用了复制算法,并行回收和STW机制对年轻代进行回收
Parallel的出现并不是多此一举,
- 与
Parallel和ParNew不同
,Parallel回收器的目标是达到一个可控制的吞吐量
,它被称为吞吐量优先
的垃圾回收器 自适应调节策略
也是Parallel scavenge 与ParNew一个重要区别
高吞吐量可以高效率地利用CPU时间,尽快完成程序地运算任务,主要适合运行在后台不要太多交互的任务,如订单处理,工资支付,科学计算等应用程序,自适应调节策略也是Parallel与ParNew的一个重要区别
Parallel回收器在JDK1.6
提供了用于老年代垃圾回收的Parallel Old回收器,用于替代Serial Old回收器,Parallel Old回收器采用标记-压缩算法,并行回收和STW机制对老年代进行垃圾回收
在程序吞吐量优先的应用场景中,Parallel和Parallel Old组合使用在Server模式下的回收性能很不错,Parallel和Parallel Old都是并行回收器均可以设置进行垃圾回收的线程数,但是回收线程数不是越多越好,太多的线程来回切换会影响垃圾回收的性能
在JDK1.8
中,默认使用Parallel 和Parallel Old
组合
6、CMS回收器:低延迟
在学习之前,先了解三色标记清除算法:三色标记清除算法
-XX:+UseConcMarkSweepGC
// 启用CMS回收器,年轻代会自动使用ParNewGC
在JDK1.5时期,HotSpot推出一款在强交互应用中划时代意义的垃圾回收器,CMS(Concurrent-Mark-Sweep) 这款回收器是HotSpot中第一款真正意义上的并发收集器,第一次实现了用户线程和回收线程同时工作
CMS的关注点是尽可能缩短垃圾回收时用户线程的停顿时间,停顿时间越短(延迟越低)越适合和用户交互的程序,良好的响应速度能提升用户的使用体验,目前很大一部分Jaba程序集中在互联网或B/S服务器端上,这类应用尤其重视服务的响应速度,希望系统停顿的时间越短越好,以给用户带来更好的体验,CMS就符合此类应用的需求。
CMS采用标记-清除算法,并发执行和STW机制对老年代进行垃圾回收
CMS的工作过程主要分为4个步骤:
初始标记,并发标记,重新标记,并发清理
6.1 初始标记(Initial-Mark)
在初始标记的过程中,工作线程都将会因为STW
出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象
,一旦标记完成之后就会恢复之前被暂停的所有用户线程。
由于直接关联的对象比较小,所有这里的速度非常快。
6.2 并发标记(Concurrent-Mark)
从GC Roots的直接关联的对象开始遍历其它挂在引用链上的对象
,这个过程耗时较长
但是不需要暂停用户线程
,可以与用户线程并发执行
6.3 重新标记(ReMark)
由于并发标记过程中,用户线程和GC标记线程并发执行,因此需要在清除前再重新标记一遍
,保证对象的引用关系正确. 重新标记无法判断并发标记阶段用户线程产生的"浮动垃圾" 这个阶段也需要进STW,时间比初始标记稍长,但比并发标记要短的多
6.4 并发清除(Concurrent-Sweep)
此阶段清理已经死亡的对象,由于不需要移动存活对象
。这个阶段也可以和用户线程并发执行
,但标记-清除算法会产生内存碎片
6.5 CMS的特点
-
尽管CMS使用并发回收,但是
初始标记
和重新标记
这两个阶段仍然需要STW
暂停用户线程,不过暂停的时间不会太长,所有的回收器都不可能不需要STW机制,只能尽量减少STW的时间,没有STW就无法保证清除时对象的引用关系不发生变化 -
由于
最耗时的并发标记和并发清除
都可以和用户线程并发执行
,因此CMS整体的回收是低延迟
的 -
由于并发清除阶段和用户线程并发执行,边回收垃圾,边制造垃圾,所以
在CMS并发清除阶段,需要确保用户线程有足够的内存可用
,因此CMS不能像其它收集器一样等到老年代被填满后再进行回收,而是当老年代使用率达到某一阈值时立刻开始工作,如果CMS运行期间预留的内存无法满足用户线程的需求,就会出现一次"Concurrent Mode Failure
"失败,临时启用Serial Old
重新对老年代进行回收 -
CMS使用
标记-清除
算法,意味着每次清除后就会产生内存碎片
,因此使用CMS回收器的堆分配空间时只能使用空闲列表
来为对象分配空间,虽然标记-压缩算法能避免内存碎片,但是却无法应用到CMS中,因为在并发清除阶段,一边清除死亡对象,一边执行用户线程,如果使用标记-压缩算法,那么堆中的对象会被移动,引用就会发生变化,正在执行的用户线程依据原先持有的对象地址可能找不到该对象
6.6 优缺点对比
优点
:
- 并发回收
- 低延迟
缺点
:
-
产生内存碎片:
如果老年代碎片化严重,此时一个大对象入堆,会提前出发full GC
-
CMS收集器对CPU资源敏感:
在并发阶段,虽然不会停止用户线程。但是会因为占用了一部分线程导致程序变慢,总吞吐量降低
-
CMS收集器无法处理浮动垃圾
在并发标记阶段由于用户线程和GC标记线程并发执行,并发标记阶段如果产生新的垃圾,CMS将无法对这些垃圾进行标记。重新标记阶段只能对原先怀疑是垃圾的对象进行判断,最终导致这些新产生的垃圾只能在下一次被回收
CMS虽然能达到不错的低延迟,但是带来了相当多的弊端,清除阶段无法整理内存,导致老年代充斥大量内存碎片,且当老年代预留内存不足时,会启用Serial Old作为临时回收器,而Serila Old是串行独占的回收器,放在现在大部分的应用场景中,如果迎来一次业务高峰,那么用户可能会感受到数秒甚至更长的停顿
在JDK9中,CMS被标记为废弃,在JDK14中CMS被删除
7、基础回收器小结
如果想要最小化使用内存和并行开销,选择Serial GC
如果想要最大化应用程序的吞吐量,选择Parallel GC
如果想要最小化GC的中断或停顿时间,选择CMS GC
8、G1回收器:区域化分代式
8.1 G1概述
几个疑问?
-
HotSpot已经内置很多回收器了,为什么还需要发布G1?
原因在于随着应用程序越来越庞大,复杂,用户越来越多 没有GC就不能保证程序正常运行,但GC经常造成的STW又跟不上实际的需求 所以才会不断对GC进行优化 G1回收器是JDK7之后引入的新的垃圾回收器,是当今垃圾回收技术发展的前沿成果之一 为了适应不断扩大的内存和不断增加的处理器数量,进一步降低延迟,同时兼顾良好的吞吐量 官方给G1设定的目标是延迟可控的情况下尽可能获得更高的吞吐量 G1担当起了全功能收集器的重任和期望
-
为什么名字叫做Garbage First(G1)?
因为G1是一个并行回收器,它把堆内存分割成很多不相关的区域(Region) 这些区域物理上可以是不连续的 使用不同的Region来表示Eden,S0,S1,Old等 G1有计划地避免了整个Java堆中进行全区域地垃圾收集 G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的时间) 在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region 由于这种方式侧重回收垃圾最多的区域,所以G1可以理解为 Garbage First 即垃圾优先
G1是一款面向服务器端应用的垃圾回收器,主要针对配备多核CPU以及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼备具有高吞吐量的性能特征
在JDK9以后,G1成为了HotSpot的默认垃圾收集器,兼顾年轻代和老年代的垃圾回收,被官方称为“全功能的垃圾收集器”
8.2 G1的优势
-
并行与并发
- 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核的工作能力,此时用户线程STW
- 并发性:G1拥有与用户线程交替执行的能力,部分工作可以和用户线程同时执行。因此一般情况下,不会在整个回收阶段发生完全阻塞应用程序的情况
-
分代分区收集
-
从分代上看,G1仍属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden和S,但从堆的角度看,它不要求年轻代或老年代是连续,也不再坚持固定大小和固定数量
-
G1将堆空间分为若干个区域(Region),这些区域包含逻辑上的年轻代和老年代
-
同时兼顾年轻代和老年代
-
-
空间整合
G1将内存划分成一个个的
Region
,内存的回收是以Region作为基本单位的,Region的回收使用复制
算法,但整体上可以看作是标记-压缩
算法,这两种算法都可以避免内存碎片,这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC,尤其当Java堆非常大时,G1的优势更加明显 -
可预测的停顿时间模型
这是G1相对于CMS的另一大优势,G1除了追求低延迟外,还能建立可预测的停顿时间模型,这能让使用者明确指定在一长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
- 由于分区的原因,G1可以只选取部分Region进行回收,这样缩小了回收的范围,因此对发生停顿的情况也能得到较好的控制
- G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,**每次根据允许的收集时间,优先回收价值最大的Region,**保证了G1在有限的时间内可以
- 相对于CMS,G1未必能做到最低的延迟,但是最差情况要好很多
8.3 G1的缺点
相较于CMS,G1还不具备全方位,压倒性优势,如用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)都要比CMS高
从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上会发挥其优势,平衡点在6-8G之间
8.4 G1的常见操作步骤
G1的设计原则就是简化JVM性能调优,只需简单三步即可完成调优:
-
开启G1垃圾回收器(JDK9之前)
-XX:+UseG1GC
-
设置堆的最大内存
-Xmx
-
设置GC最大停顿时间
-XX:MaxGCPauseMillis //默认是200ms,JVM会尽力实现,但不保证一定达到
G1中提供了三种垃圾回收模式,YoungGC,Mixed GC,Full GC,在不同的条件下被触发
8.5 G1的适用场景
- G1主要面向服务器端应用,针对具有大内存,多处理器的机器(在普通大小的堆中表现并不出色)
- 最主要应用于需要降低延迟,并且堆很大的程序,G1通过每次只清理一部分而不是全部的Region的增量式清理来保证每次GC停顿时间不会过长
- 替换CMS回收器,在下面情况下,使用G1会比使用CMS更好
- 超过50%的Java堆被活动数据占用
- 对象分配频率或年代提升频率变化很大
- GC停顿时间过长(长于0.5s到1s)
- 在HotSpot中的所有垃圾收集器,除了G1外,其它回收器均使用其内置的JVM线程执行GC操作,而G1可以使用用户线程承担后台运行的GC工作,即当JVM的GC线程处理速度变慢时,系统会调用用户线程来帮助加速垃圾回收过程
8.6 Region的介绍
使用G1收集器时,它将整个Java堆划分成2048
个大小相同的独立的Region,每个Region的大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间
,且为2的N次幂
,可以通过**-XX:G1HeapRegionSize**设定,所有的Region大小相同,且在JVM生命周期内不会被改变
虽然仍保留年轻代和老年代的概念,但年轻代和老年代不再是物理隔离的了,它们都是一部分Region(可以不连续)集合,通过Region的动态分配方式实现逻辑上的连续
一个Region可能属于Eden,S,或Old,但是一个Region活跃时只能属于一种区域,不能同时既作Eden又作Old,图中E表示该区域属于Eden,S表示属于Survivor,O表示属于Old,H表示Humongous主要存储大对象,如果一个对象的大小超过1.5个Region,就存放到H,空白表示尚未使用的内存空间,在Region中分配对象时,采用指针碰撞和TLAB
设置H的原因:
对于堆中的大对象,默认会被直接分配到老年代。但是如果它是一个短期存在的大对象,就会对垃圾回收器造成负面影响。为了解决这个问题,G1划分了Humongous区,用它专门存储大对象,如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储。为了找到连续的H区,有时不得不启动Full GC。G1的大多数行为都把H区作为老年代的一部分看待
8.7 G1回收器的回收环节
G1的垃圾回收主要包括三个环节:
-
年轻代GC (Young GC)
-
老年代并发标记过程(Concurrent Marking) 同时进行年轻代GC
-
混合回收(Mixed GC) 进行年轻代GC和老年代GC
Young GC -> Young GC + Cocurrent Marking -> Mixed GC
如果需要,单线程,独占式,高强度的Full GC还是存在的, 它针对GC的评估失败提供了一种失败保护机制,即强力回收
G1的垃圾回收过程:
-
当年轻代Eden区内存用尽时,启动Yong Gc,G1的Young GC是一个并行的独占式回收器,暂停所有用户线程,启动多GC线程回收年轻代,然后将年轻代存活对象移动到S区或Old区
-
当堆内存使用到达一定值时(默认45%),开始老年代并发标记过程。并发标记过程进行时同时进行Young GC,GC线程将老年代存活对象标记并与用户线程并发执行
-
标记完成后马上开始混合回收
G1将老年代中存活的对象移动到空闲空间,这些空闲空间就成了老年代的一部分。
和年轻代不同,G1的老年代回收器不需要回收整个老年代,一次只需扫描/回收一小部分老年代的Region,同时进行Young GC
9、G1垃圾回收过程详细说明
9.1 年轻代GC
JVM启动时,G1会先准备好Eden区,程序运行过程中不断创建对象到Eden中,当Eden空间耗尽后,G1会启动一次Young GC,Young GC只会回收Eden区和Survivor区
Young GC时,首先GC会STW停止用户线程的执行,G1创建回收集,回收集是指需要被回收的内存分段的集合,年轻代回收过程包括Eden区和Survivor区所有内存分段
Young GC的具体过程:
-
扫描根
将GC Root和Rset作为GC Roots,作为可达性分析算法的根
-
更新Rset
更新完Rset后,Rset可以准确反映出老年代对年轻代对象的引用
-
处理Rset
识别被老年代对象指向的Eden中的对象,这些对象被认为存活
-
复制对象
遍历GC Roots,Eden中存活对象被复制到Survivor区,Survivor区存活且年龄尚未超出阈值的对象年龄加1,年龄超出阈值的对象赋值到Old
-
处理引用
处理Soft,Weak,Phantom,Final,JNI Weak等引用,最终清空Eden,GC停止工作
9.2 并发标记过程
-
初始标记
标记从根节点直接可达的对象,此时STW,并触发一次Young GC
-
根区域扫描
扫描Survivor区直接可达的老年代对象,并标记被引用的对象
-
并发标记
在整个堆中进行并发标记与用户线程并发执行,若发现Region中所有对象都是垃圾,则该区域会被立刻回收,并发标记阶段会计算每个Region的对象活性(区域中存活对象的比例)
-
重新标记
STW修正并发标记的结果,G1采用了比CMS更快的初始快照算法(SATB)
-
独占清理
计算各个区域的存活对象和GC回收比例,并进行排序,是STW的,识别可以混合回收的区域,这个阶段不会真正回收
-
并发清理
识别并清理完全空闲的空间
9.3 混合收集
当越来越多垃圾进入到老年代时,需要进行一次Mixed GC,避免内存被耗尽
Mixed GC会进行一次Young GC回收整个年轻代,并回收一部分老年代,而不是全部老年代,从而对GC时间进行控制
9.4 Full GC
GC的初衷就是避免Full GC的出现,如果经过上述三个环节后不能正常工作,G1会STW,并使用单线程的内存回收算法进行垃圾回收,性能会非常差,延迟很高
导致Full GC的原因可能有两个:
- ,没有足够的Region作为S区存放经过Young GC后Eden中存活的对象
- 并发标记过程完成前空间耗尽
增加堆内存大小,能有效回避Full GC
10、经典垃圾回收器总结
截至JDK1.8,一共有7款不同的垃圾回收器,每一款都有不同的特点,在具体使用时,需要根据具体的情况选择不同的垃圾回收器
如何选择垃圾回收器?
-
优先调整堆的大小让JVM自适应完成
-
如果堆内存小于100M,使用串行回收器
-
如果是单核,单机程序,且对延迟不敏感,使用串行回收器
-
如果是多核,需要高吞吐,选择并行回收器
-
如果是多核,追求低延迟,使用并发回收器
-
现在互联网项目基本都是使用G1,能控制延迟并且保持高吞吐
没有最好的万能的回收器,调优只针对特定场景,特定需求,不存在一劳永逸的回收器
11、新时期的垃圾回收器
11.1 垃圾回收器的新发展
GC仍然在不断地发展,G1也在不断地进行改进,例如原先G1的Full GC在JDK10之后已经是并行运行,在很多场景下,其表现还略优于Parallel GC的并行Full GC实现
即使是Serial GC,虽然比较古老,但是简单的设计和实现未必就是过时的,它本身的开销,不管是GC相关数据的开销,还是数据结构的开销,还是线程的开销,都是非常小的,随着云计算的兴起,在Serveless等新的应用场景下,Serial GC找到了新的舞台
比较不幸的是CMS,因为其算法存在的缺陷,虽然现在还有比较多的程序使用,但是在JDK9种已经被标记为废弃,并在JDK14中移除
11.2 Epsilon回收器和Shenandoah回收器
Epsilon回收器
:又称为"无操作回收器
",它只做内存的分配,不做垃圾的回收。适用于某些分配好空间,执行完就结束的短时间应用
Shenandoah回收器
:称为"可伸缩的低延迟垃圾回收器
"。主打低延迟的特点,宣称延迟时间与堆大小无关
11.3 革命性的ZGC
ZGC与Shenandoah目标高度相似,在尽可能对吞吐量影响不大的情况下,实现在任意堆大小下都可以把GC延迟时间控制在10ms之内
ZGC是一款基于Region内存布局,不设分代的,使用了读屏障,染色指针和内存多重映射等技术来实现可并发的标记-压缩算法的,以低延迟为首要目标的垃圾回收器
ZGC几乎在所有环节都是并发执行的,除了初始标记是STW的,所以停顿时间几乎都耗费在了初始标记上,这部分时间实际是非常少的