本文基于《jvm性能权威指南》,总结了gc调优的相关知识点。然后希望能帮助大家快速上手jvm gc调优。
这里不对垃圾收集的原理进行详细介绍,只与性能调优相关。
一、垃圾收集器
1.各垃圾收集器主要特点
垃圾收集器 | 主要特点 | 启用参数 |
---|---|---|
Serial垃圾收集器 | Client模式默认垃圾收集器; 单线程回收; MinorGC和FullGC都会暂停应用线程。 | -XX:+UseSerialGC |
Parallel垃圾收集器 | Server模式默认垃圾收集器(jdk9前); 使用多线程回收; MinorGC和FullGC都会暂停应用线程,FullGC会对老年代空间进行压缩整理。 | -XX:+UseParallelGC, -XX:+UseParallelOldGC |
CMS垃圾收集器 | 使用多线程回收; MinorGC和老年代并发回收阶段暂停应用线程,会有空间碎片化问题。 | -XX:+UseParNewGC, -XX:+UseConcMarkSweepGC |
G1垃圾收集器 | jdk9后默认垃圾收集器; 使用非连续的区块的方式管理堆内存; 多线程并行回收; MinorGC和老年代并发回收部分阶段暂停应用线程。 | -XX:+UseG1GC |
2.如何选择垃圾收集器
- 小内存应用直接使用Serial,稳定高效
- 大部分应用在Parallel和CMS中选择:
- Parallel+Parallel old组合主要关注吞吐量,适用批处理任务
- 在CPU资源充足的情况下使用CMS(CMS只使用老年代,需要配合serial/parallel new)
- CPU资源受限时CMS会发生并发模式失效的问题,退化到Serial Old
- 一般情况下,堆空间小于4G时,CMS比G1性能好,但超大堆下G1性能更好
二、GC调优基础
1.分析工具
所有调优都是建立在对堆内存的使用情况有一定的了解的基础上,所以我们首先需要使用一些工具对我们的java应用进行监控和分析,来了解到底出现了什么问题:
- GC日志
- -XX:+PrintGCDetails:打印GC日志
- -XX:+PrintGCTimeStamps:GC日志打印时间戳
- 堆实时监控
- 使用jstat -gcutil PID 1000 指令:每秒打印堆内存实时信息及GC情况
2.调整堆大小
- 考虑维度:
- GC频率:过频繁时需要增大堆大小
- 单次GC耗时:耗时过长需要考虑控制堆大小
- GC在整个时间中所占百分比
- FullGC后剩余可用空间:普遍经验为70%可用
- 调优参数:
- -Xms=N、-Xmx=N:堆初始值和最大值
- -Xmn=N:设置固定新生代大小
- -XX:NewRatio=N:设置新生代与老年代空间占用比(此值为分母)
- -XX:NewSize=N、-XX:MaxNewSize=N:设置新生代初始和最大值
3.永久代和元空间
- 元空间默认会使用尽可能多的空间,因此不用过于关注
- 永久代(jdk7及之前)
- -XX:PermSize=N、-XXMaxPermSize=N:设置固定永久代大小和最大值
- 元空间(jdk8以后)
- -XX:MetaspaceSize=N、-XX:MaxMetaspaceSize=N:设置固定元空间大小和最大值
4.控制并发
- 控制线程数:-XX:ParallelGCThreads=N,影响多线程操作:
- Parallel 收集新生代和老年代
- CMS:新生代收集(ParNewGC)、并发收集STW阶段(非FullGC)
- G1 新生代收集、STW阶段(非FullGC)
- 默认线程数计算(n为cpu线程数):ParallelGCThreads = 8 + ( ( N-8 ) * 5 / 8 )
- 当机器CPU线程数比较多,此时默认线程数会比较大,而又同时运行多个jvm实例,这时需要手动控制线程数,避免过多垃圾回收线程并发运行。
5.自适应调整
JVM会自己根据以往的性能历史进行性能参数调整
- -XX:-UseAdaptiveSizePolicy:关闭自适应调整功能(默认开启)
- -XX:+PrintAdaptiveSizePolicy:打印自动调整信息
三、进阶调优
1. Parallel GC
- Parallel收集器会根据指标自适应调整堆大小
- -XX:MaxGCPauseMillis=N:设置最大停顿时间
- 倾向于降低老年代大小,可能会触发频繁FullGC
- -XX:GCTimeRatio=N:设置应用线程运行时间与垃圾回收时间之比(默认99)
- N = 应用时间占比 / (1 - 应用时间占比 )
- N=99时,表示GC占总时间1%
- 倾向于增大堆大小
- -XX:MaxGCPauseMillis=N:设置最大停顿时间
2. CMS
- 退化为FullGC的情况:
- 并发模式失效:新生代发生垃圾回收,而老年代没有足够空间容纳晋升对象
- 晋升失败:老年代有足够空间容纳晋升对象,但是由于空间碎片化导致晋升失败
- 永久代空间用尽
- 控制并发周期启动时机
- -XX:+UseCMSInitiatingOccupancyOnly:不自动调整CMS垃圾收集周期(默认false)
- -XX:CMSInitiationOccupancyFraction=N:触发并发收集周期的老年代空间占用比阈值(默认70)
- 不要将阈值设置得过低,至少要比堆内活跃数据数多10%~20%,频繁的并发周期中的STW会导致总体停顿过多
- 调整CMS后台线程
- -XX:ConcGCThreads=N:后台线程数目
- 默认根据ParallelGCThreads值计算得来:N = ( 3 + ParallelGCThreads ) / 4
- -XX:ConcGCThreads=N:后台线程数目
3. G1
- 退化为FullGC的情况:
- 并发模式失效:(参考cms)
- 晋升失败:(参考cms)
- 疏散失败:进行新生代垃圾收集时,Suvivor空间和老年代空间没有足够空间容纳幸存对象
- 巨型对象分配失败:对象所需内存过大,超过了单个区块内存大小(或其他限制)
- 调整G1后台线程数
- -XX:ConcGCThreads=N:后台线程数目
- 默认根据ParallelGCThreads值计算得来:N = ( 2 + ParallelGCThreads ) / 4
- -XX:ConcGCThreads=N:后台线程数目
- 调整垃圾收集频率
- -XX:InitiationHeapOccupancyPercent=N:触发并发收集周期的堆内存占用比阈值(默认45)
- 调整混合式垃圾收集周期
- -XX:G1MixedGCLiveThresholdPercent=N:触发分区回收标记的分区垃圾占比阈值
- -XX:G1MixedGCCountTarget=N:最大混合式GC周期数(默认8)
- 减少此值可以帮助解决晋升失败问题,代价是混合式GC周期停顿时间更长
- -XX:MaxGCPauseMillis=N:设置最大停顿时间(默认200ms)
4. Survivor空间及晋升:
- 设置Survivor空间大小
- -XX:InitialSurvivorRatio=N:初始Survivor空间大小占比(N为分母)
- survivor空间大小 = new_size / ( InitialSurvivorRatio + 2 )
- -XX:MinSurvivorRatio=N:Survivor最大大小(注意由于N是分母,虽然参数是min,但是却是设置的最大值)
- -XX:InitialSurvivorRatio=N:初始Survivor空间大小占比(N为分母)
- 自动调节
- -XX:TargetSurvivorRatio=N:垃圾回收后空闲空间占比
- 晋升阈值:对象在Survivor空间之间来回移动多少个GC周期后晋升到老年代
- -XX:InitialTenuringThreshold=N:初始晋升阈值
- -XX:MaxTenuringThreshold=N:最大晋升阈值
- JVM会持续的计算,在1和最大晋升阈值中选择合适的值