深入理解JVM(七)——JVM的垃圾收集器介绍、配置和选型

苏凌峰

0.前言

1.Serial垃圾收集器

1.1Serial垃圾收集器优缺点及适用场景

1.2Serial垃圾收集器的开启和使用

2.ParNew垃圾收集器

2.1ParNew垃圾收集器优缺点及适用场景

2.2ParNew垃圾收集器的开启和使用

3.Parallel垃圾收集器

3.1Parallel垃圾收集器优缺点及适用场景

3.2Parallel垃圾收集器的开启和使用

4.CMS垃圾收集器

4.1CMS垃圾收集器优缺点及适用场景

4.2CMS垃圾收集器的开启和使用

4.3CMS垃圾收集器的使用注意事项

5.G1垃圾收集器

5.1G1垃圾收集器优缺点及适用场景

5.2G1垃圾收集器的开启和使用

6.垃圾收集器的算法汇总

0.前言
我们之前在深入理解JVM(五)——垃圾收集算法中介绍过各种垃圾收集的算法,但是GC算法是内存回收的理论,垃圾收集器是算法的落地实现。首先到目前为止并没有完美的垃圾收集器,我们只有针对合适的场景,采用适当的垃圾收集器
image.png

1.Serial垃圾收集器
Serial垃圾收集器是最基本,发展最悠久的单线程垃圾收集器。我们先来看一下它的垃圾收集过程图。

image.png

执行流程:假设我们的java进程的堆内存已经被耗尽,当Serial垃圾收集器启动的时候,它会停止一切用户线程,到达一个STOP-THE-WORLD(会有较长的停顿)的状态,等到垃圾收集完毕之后,再运行用户线程进行工作。

串行收集器是最古老,最稳定且效率最高的收集器,只使用一个垃圾收集器去回收会在其收集的时候产生较长的停顿,虽然要暂停所有其它的线程,但是它最简单高效,对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率。

1.1Serial垃圾收集器优缺点及适用场景
优点:实现简单,稳定,单线程效率最高
缺点:会有较长时间的停顿,用户体验差
适用场景:单CPU或小内存,较小的程序

1.2Serial垃圾收集器的开启和使用
-XX:+UseSerialGC 开启Serial垃圾收集器
-XX:+PrintCommandLineFlags 启动服务的时候,查看程序使用的默认JVM参数

开启上述参数后,会使用Serial(Young区)+Serial(Old区)的组合。
新生代采用复制算法,老年代采用标记整理算法。

当我们把上述参数输入IDEA之后,在启动服务的第一行,就会出现我们已经开启了Serial垃圾收集器。

image.png

2.ParNew垃圾收集器
ParNew垃圾收集器其实就是多线程版本的Serial垃圾收集器,其余的行为和Serial垃圾收集器一模一样,ParNew收集器在垃圾收集过程中同样也需要暂停所有工作线程(STOP-THE-WORLD)。

image.png
2.1ParNew垃圾收集器优缺点及适用场景
优点:实现简单,稳定,在多cpu的情况下,比单线程效率更高,停顿时间短
缺点:也会有一定的停顿,用户体验差
适用场景:多CPU,追求停顿时间,需要快速响应的场景,如互联网应用

2.2ParNew垃圾收集器的开启和使用
-XX:+UseParNewGC 开启ParNew垃圾收集器
-XX:+PrintCommandLineFlags 启动服务的时候,查看程序使用的默认JVM参数

开启上述参数后,会使用ParNew(Young区)+Serial(Old区)的组合。新生代采用复制算法,老年代采用标记整理算法。

image.png

3.Parallel垃圾收集器

Parallel垃圾收集器类似于ParNew垃圾收集器,也是一个新生代的垃圾收集器,使用复制算法,也是一个并行的多线程垃圾收集器,不过它的关注点是达到可控制的吞吐量
(吞吐量:cpu用于运行用户代码的时间和cpu消耗的总时间的比值 )
(吞吐量公式:用户代码的时间 / (用户代码的时间)+(垃圾回收的占用时间))

比如程序运行100分钟,垃圾收集器运行1分钟,吞吐量就是99%。

image.png

3.1Parallel垃圾收集器优缺点及适用场景
优点:多线程收集速度快,可以控制吞吐量
缺点:如果参数设置地过于小,线程之间不断切换意味着需要额外的开销,从而垃圾回收和用户线程的总时间将会延长。
使用场景:多CPU,需要大吞吐量,如后台计算应用

3.2Parallel垃圾收集器的开启和使用
-XX:+UseParallelGC 开启Parallel垃圾收集器
-XX:+PrintCommandLineFlags 启动服务的时候,查看程序使用的默认JVM参数

image.png
因为parallel垃圾收集器是控制吞吐量的,所以还有几个重要的参数。

-XX:+UseAdaptiveSizePolicy 设置Parallel Scavenge收集器具有自适应调节策略。
在这种模式下,年轻代的大小、Eden和Survivor的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点。

在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMIllis),让虚拟机自己完成调优工作。

-XX:ParallelGCThreads设置年轻代并行收集器的线程数。一般的最好与CPU数量相等,以避免过多的线程数影响垃圾回收性能。88
默认情况下,当CPU数量小于8个,ParallelGCThreads的值等于CPU数量。
当CPU数量大于8个时,线程数为3 + ((5*CPU_Count)/8)。

-XX:MaxGCPauseMillis设置垃圾收集器最大停顿时间(即STW的时间)。单位是毫秒。
为了尽可能地把停顿时间控制在MaxGCPauseMillis以内,收集器在工作时会调整Java堆大小或者其他一些参数。

对于用户来讲,停顿时间越短体验越好。但是在服务器端,我们注重高并发,整体的吞吐量。所以服务器端适合Parallel,进行控制。

该参数谨慎使用。

-XX:GCTimeRatio :取值范围(0,100)垃圾收集时间占总时间的比例(=1 / (N + 1))用于衡量吞吐量的大小。默认值99,也就是垃圾回收时间不超过1%。
MaxGCPauseMIllis越大,这个比例就越高。

4.CMS垃圾收集器
CMS(Concurrent Mark Sweep:并发标记清除)是一种以最短回收停顿时间为目标的收集器。它的垃圾收集线程和用户线程一起执行,将STW的时间到达最短,采用标记-清除算法。

image.png

我们从图可以看出,cms垃圾收集器一共分四个步骤:
初始标记:需要stop the world,使用可达性分析算法标记,速度很快
并发标记:多个线程同时并发标记
重新标记:修正并发标记期间因为用户程序继续运作而导致的变动
并发清除:并发进行清除

4.1CMS垃圾收集器优缺点及适用场景
优点:并发收集,低停顿
缺点:对cpu资源敏感,无法处理浮动垃圾,基于标记清除,会产生碎片
适用场景:适用于堆内存大,CPU核数多的服务器端应用,这类应用及其重视服务器响应速度,希望系统停顿时间最短。

4.2CMS垃圾收集器的开启和使用
-XX:+UseConcMarkSweepGC 开启CMS垃圾收集器
-XX:+PrintCommandLineFlags 启动服务的时候,查看程序使用的默认JVM参数

image.png

image.png

4.3CMS垃圾收集器的使用注意事项
1.由于并发执行,CMS在收集与应用线程同时执行的时候,会增加堆内存的占用。也就是说,CMS垃圾收集器要在老年代堆内存用尽之前完成垃圾回收,否则CMS垃圾收集器回收失败,会触发担保机制,串行老年代收集器将会以STWGC的方式进行一次GC,造成大量停顿时间。

2.标记清除算法无法整理碎片,老年代会随着时间的流逝而用尽内存,最后不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompation来指定多少次CMS垃圾收集之后,进行一次压缩GC。

5.G1垃圾收集器
在介绍G1收集器之前,我们先总结一下以前的垃圾收集器的共同点,再来看G1。
年轻代和老年代是各自独立且连续的内存块。
年轻代收集使用eden+S0+S1进行复制算法。
老年代收集器必须扫描整个老年代区域
都是以尽可能少而快速地执行GC为设计原则。

这时,我们来看G1:
G1应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能地满足垃圾收集器暂停时间的要求。另外,它还具备以下特性:

像CMS垃圾收集器一样,能与工作线程同时并发执行。
整理空间速度更快。
不希望牺牲大量的吞吐性能。
不需要更大的JAVA Heap。

G1垃圾收集器的初衷是为了取代CMS垃圾收集器,它同CMS相比,在以下方面表现更出色。
G1是一个采用标记-整理算法,不会产生很多内存碎片
G1能充分利用多CPU硬件优势,缩短STW时间
宏观上G1不区分新生代和老年代,把内存划分成了多个独立的子区域。可以看做围棋的棋盘图。
G1收集器里面的整个内存区域都混合在一起了,但其本身仍然在小范围内仍要进行年轻代和老年代的区分。
G1虽然也是分代收集器,但整个内存分区不在物理上进行隔离,每个区域会随着对象的变化进行老年代和新生代之间的转化。

G1大概就把内存分成了如图所示

image.png

各个区域也由原来的发生了变化:

image.png

针对Eden区域进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片
假设Eden区域内存不够,部分对象晋升到Survivor区域,如果Survivor区域不够,字节进入老年代。

image.png

执行过程:和CMS一样
初始标记:需要stop the world,使用可达性分析算法标记,速度很快
并发标记:多个线程同时并发标记
重新标记:修正并发标记期间因为用户程序继续运作而导致的变动
并发清除:并发进行清除

image.png

5.1G1垃圾收集器优缺点及适用场景
优点:
G1不会产生内存碎片
G1效率高
G1可以精确控制停顿
缺点:
G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。

5.2G1垃圾收集器的开启和使用
-XX:+UseG1GC 开启G1收集器
-XX:+PrintCommandLineFlags 启动服务的时候,查看程序使用的默认JVM参数

image.png

另外 G1还有以下辅助参数。

image.png

-XX:G1HeapRegionSize=n: 设置G1区域的大小,值是2的次数幂,范围是1MB~32MB

-XX:MaxGCPauseMillis GC最大停顿时间,单位是毫秒

-XX:InitiatingHeapOccupancyPercent:堆占用了多少个的时候就触发GC,默认为45

-XX:ParallelGCThreads STW期间,并行GC线程数

-XX:G1ReservePercent 默认10%。也就是老年代会预留10%的空间来给新生代的对象晋升.设置空闲的空间预留内存百分比,以降低OOM的风险。

6垃圾收集器的算法汇总

参数新生代垃圾收集器新生代算法老年代垃圾收集器老年代算法
-XX:+UserSerialGCSerialGC复制SerialOldGC标记整理
-XX:+UseParNewGCParNew复制SerialOldGC标记整理
-XX:+UseParallelGCParallel复制ParallelOld标记整理
-XX:+UseConcMarkSweepGCParNew复制CMS+SerialOld标记清除
-XX:+UseG1GCG1整体上采用标记-整理算法局部是复制算法,不会产生碎片

总结:
今天我们介绍了各种垃圾收集器的特点,收集过程,收集算法和使用场景,希望我们能多多使用,掌握垃圾收集器。

阅读 1.4k

你的迷惑在于想得太多而书读的太少。

10 声望
14 粉丝
0 条评论

你的迷惑在于想得太多而书读的太少。

10 声望
14 粉丝
文章目录
宣传栏