# 垃圾收集器
常见的垃圾收集器有三种:
串行收集器 Serial:Serial、Serial Old
并行收集器 Parallel:Parallel Scavenge、Parallel Old 以吞吐量优先
并发收集器 Concurrent:
- CMS
- G1
停顿时间优先,也就是响应时间有限
# 串行收集器
单线程的,发现 jvm 内存不够用,暂停应用程序的执行,执行垃圾回收,回收完成之后,再继续执行应用程序
主要用于嵌入式的小型内存场景中。
# 并行 VS 并发
垃圾收集器中的并行并发并不是平时高并发中的并发。
并行(Parallel)
指多条垃圾收集线程并行工作,但此时 用户线程仍然处于等待状态。
适合科学计算、后台处理等弱交互场景。
并发(Concurrent)
指用户线程与垃圾收集器线程同时执行(但不定是并行的,可能会交替执行),垃圾收集线程在执行的时候不会停顿用户程序的运行。
适合对响应时间有要求的场景,比如 Web
# 停顿时间 VS 吞吐量
垃圾收集器中的吞吐量也不是平时高并发中的吞吐量
停顿时间
垃圾收集器做垃圾回收中断应用执行的时间。
-XX:MaxGCPauseMillis
吞吐量
花在 垃圾收集 的时间和花在 应用时间 的占比。
-XX:GCTimeRatio=<n>
,垃圾收集时间占1/1 + n
评判一个垃圾收集器的好坏:在吞吐量最佳的时候,停顿时间最少。但是在现实中,这两个指标很难同时做到都优秀,一般都是互斥的。
# 串行收集器
开启串行收集器:
-XX:+UseSerialGC
:这个是对新生代开启串行,但是会将老生代默认启用下面的参数,也就是老生代的串行 GC-XX:+UseSerialOldGC
# 并行收集器
它是以 吞吐量优先 的垃圾收集器,开启方式:
-XX:+UseParallelGC
-XX:+UseParallelOldGC
该收集器在 Server 模式下是默认的收集器。
什么是 Server 模式?
jvm 会根据当前机器的配置判定开启 server 模式还是 client 模式,一般来说,机器内存大于 2G,就会启用 server 模式
下面可以来看下我们启动的应用程序默认启用的垃圾收集器
mrcode@mrcode ~ % jps -l
20572 org.gradle.launcher.daemon.bootstrap.GradleDaemon
20575 cn.mrcode.stady.monitor_tuning.MonitorTuningApplication
mrcode@mrcode ~ % jinfo -flag UseParallelGC 20575
-XX:+UseParallelGC
2
3
4
5
6
# 并发收集器
是以响应时间有限的垃圾收集器。在 JDK 中有两种并行的收集器,开启方法如下:
# CMS
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
# 首先看现在的应用,该收集器并没有开启
mrcode@mrcode ~ % jinfo -flag UseConcMarkSweepGC 20575
-XX:-UseConcMarkSweepGC
# 添加启动参数,并重启程序后,再次观察
mrcode@mrcode ~ % jinfo -flag UseConcMarkSweepGC 20801
-XX:+UseConcMarkSweepGC
2
3
4
5
6
7
# G1
开启方式:-XX:+UseG1GC
# 垃圾收集器搭配
在不同的区里面使用的垃圾收集器不同,前面讲过,JDK 使用的是分代垃圾回收方式
两个区里面的收集器不一样,但是上线有连线的是成对搭配使用的。在 JDK8+ 中,建议使用 G1 收集器,性能较高
其中虚线是在某些情况下,CMS 会退化成 SerialOld 收集器。我们课程的终点是 G1 收集器。
# 如何选择垃圾收集器
一般有如下建议:
- 优先调整堆的大小,让服务器自己来选择
- 如果内存小于 100M,使用串行收集器
- 如果是单核,并且没有停顿时间的要求,串行或则 JVM 自己选择
- 如果允许停顿时间超过 1 秒,选择并行或则 JVM 自己选择
- 如果响应时间最重要,并且不能超过 1 秒,使用并发收集器
官方文档中有讲解,在 JDK8 (opens new window) 页面中的 HotSpot Virtual Machine Garbage Collection Tuning Guide (opens new window) 中,专门讲解如何进行 GC 调优。非常详细
# Parallel Collector
-XX:+UseParallelGC
手动开启Server 模式默认开启
-XX:ParallelGCThreads=<N>
使用 n 个 GC 线程默认 n 为以下计算方式:
CPU > 8
:N=5/8
CPU < 8
:N=CPU
垃圾收集器有一个自适应功能(Ergonomics),比如设置以下三个参数:
-XX:MaxGCPauseMillis=<N>
-xx:GCTimeRatio=<N>
-Xmx<N>
它会按照顺序平衡前两个。也就是会进行动态的调整各个区的大小
# 动态内存调整
-XX:YoungGenerationSizelncrement=<Y>
:增加 Young 区内存大小,默认值为 20%-XX:TenuredGenerationSizelncrement=<Y>
:增加 Old 区内存大小,默认值为 20%-XX:AdaptiveSizeDecrementScaleFactor=<Y>
::减少内存大小,默认值为 4%
# CMS Collector
它是一个 并发收集,低停顿、低延迟,是一个老年代收集器
# CMS 垃圾收集过程
CMS initial mark:初始标记 Root,STW (停止应用程序)
CMS concurrent mark:并发标记
CMS-concurrent-preclean:并发预清理
CMS remark:重新标记,STW(停止应用程序)
此时应用程序还在运行,有可能又有垃圾了,所以重新标记
CMS concurrent sweep:并发清除
CMS concurrent reset:并发重置
# CMS 缺点
CPU 敏感
比如设置一个并发线程,那么只有双核的时候,垃圾收集器就会占用其中一个,这个时候就只有一个核心为应用工作了
浮动垃圾:会产生浮动垃圾
因为程序还在运行,一边回收,一边分配,就会产生浮动垃圾
空间碎片
# CMS 的相关参数
-XX:ConcGCThreads
:并发的 GC 线程数-XX:+UseCMSCompactAtFullCollection
:FullGC 之后做压缩上面说会产生碎片,所内存压缩就相当于在清除空间碎片
-XX:CMSFullGCsBeforeCompaction
:多少次 FUllGC 之后压缩一次-XX:CMSInitiatingOccupancyFraction
:触发 FullGCOld 区在填满多少存活对象的时候触发一次 FullGC,该值默认值应该是 90%+
-XX:+UseCMSInitatingOccupancyOnly
:是否动态调整-XX:+CMSScavengeBeforeRemark
:FullGC 之前先做 YGC因为在 YGC 之后,会回收一部分垃圾,再做 Old GC 的时候就会减少很多工作。
笔者疑问:但是不是说是分代垃圾回收吗?怎么还会跨区影响?
-XX:CMSClassUnloadingEnabled
:启用回收 Perm 区JDK7 以前有该区
如果没有设置,显示 -1,在文档中有说明它的默认值是多少。
mrcode@mrcode ~ % jinfo -flag CMSInitiatingOccupancyFraction 20572
-XX:CMSInitiatingOccupancyFraction=-1
2
CMS 还有一个 iCMS,适用于单核或则双核,因为核数少时,垃圾比较多,回收一次占用时间过长,就会影响应用程序,iCMS 就是为了优化这一点,分多次回收。
# G1 Collector
JDK8 中支持的垃圾回收器,不断的在进化,在 JDK11 中又有了其他的垃圾回收器
Garbage-First(G1) (opens new window) 垃圾收集器是一种服务器样式的垃圾收集器,适用于具有大内存的多处理器计算机。它试图以高概率满足垃圾收集(GC)暂停时间目标,同时实现高吞吐量。全堆操作(例如全局标记)与应用程序线程同时执行。这样可以防止与堆或活动数据大小成比例的中断。
G1 收集器通过多种技术实现了高性能和暂停时间目标。
G1 的首要重点是为运行需要大堆且 GC 延迟有限的应用程序的用户提供解决方案。这意味着堆大小约为 6 GB 或更大,并且稳定且可预测的暂停时间低于 0.5 秒。
它同时是新生代和老生代收集器
它是将堆划分成一块一块的,在逻辑上某些块组成了各种数据区,比如 Old 区。
# G1 的概念
Region:比如上图中的 H,几个块组成的一个区
SATB:Snapshot-At-The-Beginning
它是通过 Root Tracing 得到的,GC 开始时候存活对象的快照,后面再做垃圾回收的时候,会以此基础作为回收
Rset:记录了其他 Region 中的对应引用本 Region 中对象的关系,属于 points-into 结构(谁引用了我的对象)
# YoungGC
- 新对象进入 Eden 区
- 存活对象拷贝到 Survivor 区
- 存活时间到达年龄阈值时,对象晋升到 Old 区
# MixedGC
回收所有的 Young 和 部分 Old,所以不是 FullGC
这一部分可以通过某些参数设置
global concurrent marking:全局并发标记
- Initial marking phase:标记 GC Root,STW
- Root region scanning phase:标记存活 Region
- Concurrent marking phase:标记存活的对象
- Remark phase:重新标记,STW
- Cleanup phase:部分 STW
MixedGC 时机,通过以下参数控制
InitiatingHeapOccupancyPercent
:堆占有率达到这个数值则触发 global concurrent marking ,默认 45%G1HeapWastePercent
:在 global concurrent marking 结束之后,可以知道区有多少空间要被回收,在每次 YGC 之后和再次发生 Mixed GC 之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生 Mixed GC。
# MixedGC 相关参数
G1MixedGCLiveThresholdPercent
Old 区的 region 被回收时候的存活对象占比
G1MixedGCCountTarget
一次 global concurrent marking 之后,最多执行 Mixed GC 的次数
G1OldCSetRegionThresholdPercent
一次 Mixed GC 中能被选入 CSet 的最多 old 区的 region 数量
# 常用参数
-XX:+UseG1GC
:开启 G1-XX:G1HeapRegionSize=n
:region 的大小,1-32M,最多 2048 个-XX:MaxGCPauseMillis=200
:最大停顿时间-XX:G1NewSizePercent
-XX:G1MaxNewSizePercent
-XX:G1ReservePercent=10
:保留防止 to space 溢出,默认是百分之 10-XX:ParallelGCThreads=n
:SWT 线程数-XX:ConcGCThreads=n
:并发线程数=1/4*并行
# 最佳实践
年轻代大小:
避免使用
-Xmn
、-XX:NewRatio
等显示设置 Young 区大小,会覆盖暂停时间目标暂停时间目标:
暂停时间不要太严苛,其吞吐量目标是 90% 的应用程序时间和 10% 的垃圾回收时间,太严苛会直接影响到吞吐量
MixGC 调优:
-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent
、-XX:G1HeapWastePercent
-XX:G1MixedGCCountTarget
-XX:G1OldCSetRegionThresholdPercent
TIP
本章笔者是第一次接触,看得有点懵逼,记录也只是硬着头皮记录下来,某些并没有看懂
后续有此需求,还是需要去阅读官方文档的调优指南来理解。
# 是否需要切换到 G1
- 50% 以上的堆被存活对象占用
- 对象分配和晋升的速度变化非常大
- 垃圾回收时间特别长,超过了 1 秒