(JVM调优)GC调优
GC调优
GC调优指的是对JVM的垃圾回收部分进行调优,其目标是避免由垃圾回收引起的程序性能下降;
其调优的核心分为三部分
- 通用JVM参数的设置
- 特定垃圾回收期的JVM参数设置
- 解决频繁的Full GC造成的性能问题
GC调优的核心指标
- 业务吞吐量:一段时间内程序需要完成的业务数量
- 垃圾回收吞吐量:CPU用于执行用户代码的时间与CPU总执行时间的比值
- 延迟:用户发起一个请求到收到相应之间经历的时间
- 内存使用量:Java程序占系统内存的最大值,在满足上面的指标的前提下,内存用量越少越好
GC调优的方法
GC调优与内存调优一样,也是分为 发现->诊断->修复->验证 四个阶段
发现阶段
我们可以通过使用jstat工具,visualvm插件,Prometheus + Grafana,观察GC日志,GC Viewer工具,GCeasy工具来帮助我们发现问题
诊断阶段
CG的正常情况
呈现锯齿状,对象创建后内存使用增加,在经过垃圾清理后大幅回落,且每次回收之后的使用内存大小接近,留存对象较少
缓存对象过多的情况
与正常情况类似,但是在清理后的内存占用仍然较高
原因:程序中保存了大量缓存对象(临时存储在内存中的数据),导致GC无法释放,可以使用MAT或者HeapHero等工具来具体分析原因
内存泄露
呈现锯齿状,每次GC之后的内存使用一次比一次多,最后完全无法释放内存,导致OutOfMemory错误
原因:程序中保存了大量的内存泄露对象,导致GC之后无法释放,同样可以使用MAT或者HeapHero等工具来具体分析原因
连续的Full GC
在某个时间点出现多次Full GC,CPU使用率拉满,无法处理用户请求,一段时间后恢复正常
原因:在该时间点请求量激增,程序开始产生大量对象,同时垃圾收集速度跟不上对象创建的速度,进而导致不断地Full GC
元空间不足导致的Full GC
发生该问题时,堆内存占用明明不高,可是还是不断的发生Full GC
原因:元空间大小不足,导致持续触发Full GC来回收元空间的数据
修复阶段
下面介绍四种解决GC的手段,第四种方案仅建议在前三种方案无法解决时使用
1.优化JVM参数
参数1:-Xmx 和 -Xms
-Xmx 设置的是最大堆内存,需要注意的是,计算可用内存时,需要将元数据、操作系统和其他软件占用的内存排除掉。eg:服务器内存4G,操作系统+元空间最大值+其它软件占用1.5G,-Xmx可以设置为2g。
-Xms 设置初始堆大小,这里建议将Xms和Xmx设置一样大,这样有以下优点
运行性能好(减少堆扩容导致的性能损耗),可用性好(与同在运行的其他程序抢占内存),启动速度快(GC次数少)
最合理的设置方式应该是根据最大并发量估算服务器的配置,然后再根据服务器配置计算最大堆内存的值。
-xx:MaxMetaspaceSize 和 -xx:MetaSpaceSice
-XX:MaxMetaspaceSize=值 参数指的是最大元空间大小,默认值比较大,如果出现元空间内存泄漏会让操作系统可用内存不可控,建议根据测试情况设置最大值,一般设置为256m。
-XX:MetaspaceSize=值 参数指的是到达这个值之后会触发FULLGC(网上很多文章的初始元空间大小是错误的),后续什么时候再触发JVM会自行计算。如果设置为和MaxMetaspaceSize一样大,就不会FULLGC,但是对象也无法回收。
-Xss 虚拟机栈大小
如果我们不指定栈的大小,JVM 将创建一个具有默认大小的栈。大小取决于操作系统和计算机的体系结构。
不建议手动设置的参数
-Xmn 年轻代的大小;在设置这个值之前需要进行大量的计算,不建议手动修改。同时如果使用G1垃圾回收器,就尽量不要使用该值,因为G1会动态地调整年轻代的大小
‐XX:SurvivorRatio 伊甸园区和幸存者区的大小比例,默认值为8。
‐XX:MaxTenuringThreshold 最大晋升阈值,年龄大于此值之后,会进入老年代。JVM有动态年龄判断机制:将年龄从小到大的对象占据的空间加起来,如果大于survivor区域的50%,然后把等于或大于该年龄的对象,放入到老年代,所以也不建议修改
其他参数
-XX:+DisableExplicitGC
禁止在代码中使用System.gc(), System.gc()可能会引起FULLGC,在代码中尽量不要使用。使用DisableExplicitGC参数可以禁止使用System.gc()方法调用
-XX:+HeapDumpOnOutOfMemoryError:发生OutOfMemoryError错误时,自动生成hprof内存快照文件。
-XX:HeapDumpPath=
:指定hprof文件的输出路径。 打印GC日志
JDK8及之前 : -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:文件路径
JDK9及之后 : -Xlog:gc*:file=文件路径
JVM参数模版
1
2
3
4
5
6
7
8
9
10-Xms1g
-Xmx1g
-Xss256k
-XX:MaxMetaspaceSize=512m
-XX:+DisableExplicitGC
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/logs/my-service.hprof
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:文件路径注意:
JDK9及之后gc日志输出修改为 -Xlog:gc*:file=文件名
堆内存大小和栈内存大小根据实际情况灵活调整。
2.减少对象产生
大多数场景下的FULLGC是由于对象产生速度过快导致的,减少对象产生可以有效的缓解FULLGC的发生
- 对对象进行复用:重复创建和销毁对象会增加GC的负担。因此,可以尽量避免频繁创建和销毁对象,而是通过对象的复用来减少对象的产生。
- 使用对象池:对象池是一种缓存对象的机制。通过预先创建一定数量的对象,并将其保存在池中,当需要对象时,从池中获取,使用完毕后再放回池中。这样可以避免频繁创建和销毁对象,减少GC的压力。
- 及时释放对象引用:在对象不再使用时,及时将其引用置为null
3.更换垃圾回收器
垃圾回收器的组合关系
由于垃圾回收器分为年轻代和老年代,所以除了G1以外的垃圾处理器需要根据年轻代和老年代进行成对组合的使用
关系图如下:
可以通过测试不同组合在特定负载要求下的性能表现,找到适用的垃圾处理器
4.优化垃圾回收器参数
尽量在前三种方法无效时再使用这种方法;具体需要根据产生性能问题的原因来进行针对性的排查。