GC调优
TODO
内容:
- 教程
- 压测+调优
- 实际样例
tip:
- collector
- gc logs
- gc viewer
- jmeter
- 压测与调优
JVM内存结构
Java8 语言规范
Java8 JVM规范
Java8 JVM规范-内存结构
运行时数据区

程序计数器
虚拟机栈JVM Stacks
栈帧分为哪些快?每块又保存什么内容?
堆
方法区
非堆
- JDK6 perm区
- JDK7 perm区
- JDK8 metaspace
常量池Run-Time Constant Pool (方法区中)
本地方法栈Native Method Stacks
JVM的内存结构

- CCS:只有启用了短指针的时候,才存在
- CodeCache:只有启用了JIT和有JNI调用Native代码的时候,才存在
-Xcomp:JIT完全编译执行-Xint完全解释执行-Xmixed编译和解释混合
非堆区

标准参数
1 | -version -showversion |
-X
-Xint: 解释执行模式
-Xcomp: 编译执行模式, 第一次使用就编译成本地代码, 编译结果保存在metaspace的code cache空间
-Xmixed: 混合执行模式, JVM决定是否编译成本地代码
1 | > java -Xint -version |
-XX
| 参数 | 作用 |
|---|---|
| -Xms | 最小堆内存 |
| -Xmx | 最大堆内存 |
| -XX:NewSize | 新生代大小 |
| -XX:MaxNewSize | 最大新生代大小 |
| -XX:NewRatio | new区和old区的比例 |
| -XX:SurvivorRatio | eden区与survivor区大小比例 |
| -XX:MetaspaceSize | Metaspace大小 |
| -XX:MaxMetaspaceSize | Metaspace最大大小 |
| -XX:+UseCompressedClassPointers | 压缩类指针 |
| -XX:CompressedClassSpaceSize | 压缩类空间(CCS)的大小,默认1G |
| -XX:InitialCodeCacheSize | code cache的初始大小 |
| -XX:ReservedCodeCacheSize | code cache的最大的大小 |
| -XX:PretenureSizeThreshold | 大对象直接进入老年代,大对象的大小阈值 |
| -XX:MaxTenuringThreshold | 长期存活的对象进入老年代,晋升年龄阈值 |
| -XX:+PrintTrnuringDistuibution | youngGC时打印年龄分布情况 |
| -XX:TargetSurvivorRatio | survivor垃圾回收存活的比例,超过值将直接晋升 |
垃圾回收算法
GC Root:
- 类加载器:由类加载器生成的对象,都持有指针
- Thread:线程运行会持有很多对象
- 虚拟机栈的本地变量表
- static成员
- 常量引用
- 本地方法栈的变量
引用计数
缺点:
无法处理循环引用
标记清除
先标记需要回收的对象,在统一回收所有对象
缺点:
效率不高:标记和清除两个过程效率都不高;碎片:导致提前GC
复制
内存划分为大小相同的两块,每次只使用其中一块,一块用完复制存活的对象到另一块,然后再把已使用的内存空间一次清理掉
缺点:
使用简单,效率高,空间利用率不高
标记整理
先标记需要回收的对象,让所有存活的对象都向一端移动,然后清理掉端边界外的内存
缺点:
无内存碎片,比较耗时
分带垃圾回收
young区朝生夕死,生命周期端,用复制算法:效率高
Old区生命周期长,用标记清除或标记整理
- 对象优先分配在eden区
- 大对象直接进入老年代:
-XX:PretenureSizeThreshold - 长期存活的对象进入老年代:
-XX:MaxTenuringThreshold: 晋升年龄代数阈值-XX:+PrintTenuringDistribution:ygc打印存活对象的分布情况-XX:TargetSurvivorRatio:Survivor区存活对象比例,动态调整,取存活对象的平均值与晋升年龄阈值间的最小值
垃圾收集器
枚举根节点,做可达性分析
根节点: 类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量
- 串行收集器Serial: Serial、 Serial old
- 并行收集器Parallel: Parallel Scavenge、Parallel old,吞吐量优先
- 并发收集器Concurrent: CMS、G1,停顿时间优先
并行 vs 并发
并行(Parallel): 多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。适合科学计算、后台处理等弱交互的场景
并发(Concurrent): 用户线程和垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾收集线程在执行的时候不会停顿用户程序的运行。适合对响应时间有要求的场景,如web。
停顿时间 vs 吞吐量
停顿时间:垃圾收集器做垃圾回收中断应用执行的时间。-XX:MaxGCPauseMillis
吞吐量:花在垃圾收集的时间和花在应用时间的占比。 -XX:GCTimeRatio=<n>, 垃圾收集时间占: 1/(1+n)
串行收集器
-XX:+UseSerialGC
-XX:+UseSerialOldGC
采用串行收集器,默认old区采用串行收集器
并行收集器 ParallelCollector
吞吐量优先
1 | -XX:+UseParallelGC |
1 | -XX:ParallelGCThreads=<N> 多少个GC线程 |
并发收集器
响应时间优先
CMS: -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
G1: -XX:+UseG1GC
如何选择垃圾收集器
- 优先调整堆的大小让服务器自己选择
- 如果内存小于100M,使用串行收集器
- 如果是单核,并且没有停顿时间的要求,串行或者jvm自己选
- 如果允许停顿时间超过1s,选择并行或者jvm自己选
- 如果响应时间最重要,并且不能超过1s,则使用并发收集器
| young | Tenured | JVM options |
|---|---|---|
| Serial | Serial | -XX:+UseSerialGC |
| Parallel Scavenge | Serial | -XX:+UseParallelGC -XX:-UseParallelOldGC |
| Parallel Scavenge | Parallel Old | -XX:+UseParallelGC -XX:+UseParallelOldGC |
| Parallel New或Serial | CMS | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC |
| G1 | G1 | -XX:+UseG1GC |

垃圾回收器从线程运行情况分类有三种
串行回收: Serial回收器,单线程回收,全程stw;
并行回收: 名称以Parallel开头的回收器,多线程回收,全程stw;
并发回收: cms与G1,多线程分阶段回收,只有某阶段会stw;
并行收集器 Parallel Collector
暂停应用程序,开启多个垃圾收集线程开始垃圾回收
-XX:+UseParallelGC手动开启,Server默认开启-XX:ParallelGCThreads=<N>多少个GC线程CPU>8 N=5/8CPU<8 N=CPU
查找使用ParallelGC的进程jps -v | grep -v grep | awk '{print $1}' | xargs -L 1 -t jinfo -flag UseParallelGC
Parallel Collector Ergonomics自适应
-XX:MaxGCPauseMillis=<N>:最大停顿时间-XX:GCTimeRatio=<N>: GC时间占比,代表吞吐量-Xmx<N>: 堆最大大小
优先满足停顿时间,再满足吞吐量的要求,最后再调整满足堆最大大小
动态调整每个分区的大小
动态内存调整
-XX:YoungGenerationSizeIncrement=<Y>年轻代大小调整增量,默认值20%-XX:TenuredGenerationSizeIncrement=<T>老年代大小调整增量,默认值-XX:AdaptiveSizeDecrementScaleFactor=<D>减少增量,默认值4%
CMS
1 | java -XX:+UseConcMarkSweepGC -jar -server console.jar |
1 | jps -l | grep buried | awk '{print $1}' | xargs -L 1 -t /usr/local/soft/jdk1.8.0_191/bin/jinfo -flags |
1 | jps -l | grep buried | awk '{print $1}' | xargs -L 1 -t /usr/local/soft/jdk1.8.0_191/bin/jinfo -flag CMSInitiatingOccupancyFraction |
cms是一种预处理垃圾回收器,它不能等到old内存用尽时回收,需要在内存用尽前,完成回收操作,否则会导致并发回收失败;所以cms垃圾回收器开始执行回收操作,有一个触发阈值,默认是老年代或永久带达到92%
- 并发收集
- 低停顿 低延迟
- 老年代收集器
CMS垃圾收集过程
- CMS inital mark: 初始标记Root STW
- CMS concurrent mark:并发标记
- CMS-concurrent-preclean: 并发预清理
- CMS remark: 重新标记 STW
- CMS concurrent sweep:并发清除
- CMS-concurrent-reset:并发重置
缺点
- 低停顿 低延迟
- CPU敏感
- 浮动垃圾:边运行应用程序,边回收
- 空间碎片
调优参数
| 参数 | 备注 |
|---|---|
| -XX:ConcGCThreads | 并发的GC线程数 |
| -XX:+UseCMSCompactAtFullCollection | FullGC之后做压缩 |
| -XX:CMSFullGCsBeforeCompaction | 多少次FullGC之后压缩一次 |
| -XX:CMSInitiatingOccupancyFraction | 触发FullGC 92% |
| -XX:+UseCMSInitiatingOccupancyOnly | 是否动态调 |
| -XX:+CMSScavengeBeforeRemark | FullGC之前先做YGC |
| -XX:+CMSClassUnloadingEnabled | 启用回收Perm区 |
G1
大内存(大于6G),优先延迟(小于0.5s)

H区:大对象,如果对象超过了region的一半大小
Region
SATB:snapshot-at-the-beginning, 通过Root tracing得到的,GC开始时候存活对象的快照。垃圾回收以此为基础回收
RSet:记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)
YoungGC
- 新独享进入Eden区
- 存活对象拷贝到s区
- 存活时间达到年龄阈值时,对象晋升到old区
mixedGC
没有full gc
- 不是FullGC,回收所有的Young和部分Old
- global concurrent marking
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:在gloabl concurrent marking结束之后,可以知道区有多少空间要被回收,在每次YGC之后和再次发生MixedGC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生MixedGC
- G1MixedGCLiveThresholdPercent: Old区的region被回收时候的存活对象占比
- G1MixedGCCountTarget:一次global concurrent marking之后,最多执行MixedGC的次数



调优最佳实践



可视化GC日志分析工具

吞吐量与延迟时间的权衡