GC分类与性能指标
分类:
- 按线程数分:串行垃圾回收器(硬件配置低,单CPU;Client模式下)和并行垃圾回收器,都会STW
- 按工作模式分:并发式垃圾回收器(交替工作)和独占式垃圾回收器
- 按碎片处理方式:压缩式垃圾回收器和非压缩式垃圾回收器(指针碰撞、维护空闲列表)
- 按工作的内存区间分:年轻代垃圾回收器和老年代垃圾回收器
性能指标:
- 吞吐量(throughput):运行用户代码的时间占总时间(程序运行时间+内存回收)的比例,越大越好
- 垃圾收集开销:吞吐量的补数
- 暂停时间(pause time):STW时间,越小越好
- 收集频率:收集操作发生的频率
- 内存占用:java堆区所占的内存大小
- 快速:一个对象从诞生到被回收所经历的时间
高吞吐量较好是因为让用户感觉到只有用户线程在工作
低延迟较好是因为让用户感觉到自己线程不被打断
目标:在最大吞吐量优先的情况下,降低停顿时间
不同的垃圾回收器概述
- 1999 Serial GC,ParNew Serial的多线程版本
- 2002 Parallel GC,Concurrent Mark Sweep GC(CMS)
- 2012 G1
- 2017 G1为默认垃圾收集器
- 2018 G1垃圾回收器的并行完整垃圾回收
- 2018.9 Epsilon 又称为No-op,ZGC(可伸缩的低延迟垃圾回收器)
- 2019.3 增加G1
- 2019.9 增强ZGC
- 2020.3 jdk14中删除CMS
7种经典垃圾回收器:
- 串行回收器:Serial,Serial Old
- 并行回收器:Parallel,Parallel Scavenge,Parallel Old
- 并发回收器:G1,CMS
组合关系:
-XX:+PrintCommandLineFlags
package chapter17;
import java.util.ArrayList;
public class PrintGCTest {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
while (true){
byte[] bytes = new byte[100];
list.add(bytes);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
hello world
Serial回收器:串行回收
Serial 采用复制算法、串行回收和 “Stop the World”机制的方式执行内存回收
Serial Old 采用标记-压缩算法、串行回收和 “Stop the World”机制的方式执行内存回收。Client模式下老年代的垃圾回收器;在Server 模式下 1)与新生代的Parallel Scavenge配合使用,2)作为老年代CMS收集器的后背垃圾收集方案。
简单高效,单核CPU会用
-XX:+UserSerialGC :新生代使用Serial GC,老年代使用Serial Old GC
-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
parNew回收器:并行回收
并行回收执行内存回收,STW机制,复制算法
在Server模式下新生代的默认垃圾回收器
ParNew运行在多CPU环境下,可以充分利用CPU,可以快速完成垃圾收集。但在单CPU环境下Serial效率更高,不需要来回切换CPU
-XX:+PrintCommandLineFlags -XX:+UseParNewGC表示新生代使用ParNew GC
-XX:ParallelGCThreads设置线程数
-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
Parallel Scavenge 回收器:吞吐量优先
Parallel采用 复制算法、并行回收、STW机制
和ParNew不同,Parallel目标是达到一个可控制的吞吐量,还有自适应调节策略
高吞吐量可以高效率的利用CPU时间,适合后台运算而不需要太多交互的任务,如批量处理,订单处理,Parallel Old 用于处理老年代来替代Serial Old,采用 标记-压缩,并行回收和STW机制。
-XX:+PrintCommandLineFlags
-XX:+UseParallelGC 新生代垃圾回收器
-XX:+UseParallelOldGC 老年代垃圾回收器
-XX:ParallelGCThreads 设置年轻代并行收集器的线程数,CPU个数小于8为8
-XX:MaxGCPauseMills 设置最大停顿时间,谨慎使用
-XX:GCTimeRatio
-XX:+UseAdaptiveSizePolicy 设置Parallel收集器的自适应调节策略,默认开启
-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
CMS回收器:低延迟
Concurrent Mark Sweep 并发收集器,并发、标记-清除算法、STW
- 初始标记:标记出GC Roots能直接关联到的对象,STW,时间较短
- 并发标记:关联到的直接对象开始遍历整个对象图的过程,并发
- 重新标记:修正并发标记期间,因用户程序继续运行导致标记产生变动的那一部分对象的标记记录,STW,时间较短
- 并发清除:清理删除掉标记阶段判断的已经死亡的对象,释放内存空间,并发
整体的回收是低延迟的
确保应用程序用户线程有足够的内存可用
当堆内存使用率达到某一阈值时,便开始运行回收,出现了Concurrent Mode Failure,临时启用Serial Old
标记清除算法,会产生内存碎片,选择空闲列表对对象分配内存空间
为什么不适用标记-压缩算法呢?
因为要保证用户线程正常执行,所以不能改变程序的地址
弊端:
- 会产生内存碎片,无法分配大对象,会触发Full GC
- 对CPU资源敏感
- 无法处理浮动垃圾:并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终导致新产生的垃圾对象没有被及时回收
-XX:+UseConcurrentMarkSweepGC 老年代使用CMS回收器
-XX:CMSlnitiatingOccupanyFraction 堆内存使用率的阈值,一旦到达阈值,开始回收。内存增长缓慢,阈值可以大一点
-XX:+UseCMSComactAtFullCollection:指定执行完Full GC后堆内存空间进行压缩整理
-XX:CMSFullGCsBeforeCompation 设置在执行多少次Full GC后对内存空间进行压缩整理
-XX:ParallelCMSThreads 设置CMS线程数量
-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544
-XX:MaxNewSize=348966912 -XX:MaxTenuringThreshold=6
-XX:OldPLABSize=16 -XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops
-XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation
-XX:+UseParNewGC
G1回收器:区域化分代式
将堆内存分割成很多不相关的区域(Region),避免在整个堆中进行全域的垃圾收集,优先回收垃圾最大量的区域。
多核CPU和大容量内存, jdk9后默认
优点:
1.并行和并发
- 并行性:可以有多个GC线程同时工作,有效利用多核运算能力,STW
- 并发性:可以与应用程序交替执行
2.分代收集
- 分代型垃圾回收器,多个region中有多个新生代多个老年代,不需要连续,不需要固定大小
- 将堆空间分成若干个区域,包含逻辑上的年轻代和老年代
- 同时兼顾年轻代和老年代
3.空间整合
- CMS 标记-清除、内存碎片,若干次GC后整理碎片
- Region之间是复制算法,不存在碎片,整体上是标记-压缩,有利于程序长时间运行
4.可预测的停顿时间模型(软实时 soft real-time)
- 分区可以只选取部分区域进行内存回收,不需要全局停顿
- 跟踪Region的价值(空间大小和所需时间价值),优先回收价值大的region,保证在有限时间内可以获得尽可能高的收集效率
缺点:
小内存应用上CMS的表现大概率优于G1,而G1在大内存应用上发挥优势
参数设置:
-XX:+UseG1GC
-XX:G1HeapRegionSize 设置每个region的大小,是2的幂,范围是1MB-32MB
-XX:MaxGCPauseMills 最大GC停顿时间指标,默认200ms
-XX:ParallelGCThread STW工作线程数
-XX:ConcGCThreads 并发标记的线程数,占ParallelGCThread 的1/4
-XX:InitiatingHeapOccupancyPercent 触发并发GC周期的堆内存阈值
G1回收器的常见操作步骤
- 第一步:开启G1垃圾回收器
- 第二步:设置堆的最大内存
- 第三步:设置最大停顿时间
G1使用场景
- 服务器端,大内存多处理器
- 低延迟并堆空间较大
- 替换原本CMS
- 采用应用线程承担后台 的GC工作
分区region:化整为零
Eden
survivor
old
humongous:矩形对象,用来存储大对象,如果一个H区存不下,那么用连续的H区来存储
指针碰撞
TLAB:线程一小份
G1垃圾回收过程:
- 年轻代GC:当年轻代的Eden区用尽时开始年轻代回收
- 当堆内存使用45%,老年代并发标记
- 混合回收
记忆集 Remembered Set
问题:一个region对象可能被其他region对象引用,判断对象是否存活时,是否需要扫描整个Java堆才能保证准确?回收新生代不得不要扫描整个老年代?
解决:给每个region附带一个Remembered Set记录谁引用了当前region,每次引用类型数据写操作时,都会产生一个写屏障(Write Barrier)暂时中断操作,将引用写入到RSet中。
G1回收过程一:年轻代GC
- 扫描根。根引用连同RSet记录的外部引用作为扫描存活对象的入口
- 更新Rset。RSet可以准确反映老年代对所在的内存分段中对象的引用
- 处理RSet
- 复制对象
- 处理引用。处理软弱虚引用
G1回收过程二:并发标记
- 初始标记。STW
- 根区域扫描。扫描survivor区直接可达的老年代区域对象,要在YGC前完成
- 并发标记。若发现区域中所有对象都是垃圾,则立即回收
- 再次标记。STW,使用初始快照算法snapshot-at-the-beginning(SATB)
- 独占清理。STW
- 并发清理
G1回收过程三:混合回收
- 并发标记结束后,老年代中百分百垃圾的内存被回收,部分为垃圾的内存分段计算出来。默认老年代的内存分段会分8次
- 混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden区内存分段,Survivor内存分段。
- 由于老年代中的内存分段默认为8次回收,G1会优先回收垃圾多的内存分段。垃圾站内存分段比例越高,越会被先回收
G1回收过程四:Full GC
导致 G1 Full GC原因:
- 没有足够多的to-space存放晋升的对象
- 并发处理过程完成之前空间耗尽
补充:回收阶段设计成与用户程序一起并发执行
G1 垃圾回收的优化建议
1.年轻代大小:
避免使用-Xmn等相关参数设置年轻代大小
固定年轻代的大小会覆盖暂停时间目标
2.暂停时间目标不要太过严苛
G1 GC的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间
评估 G1 GC的吞吐量时。暂停时间目标不要太严苛,会影响到吞吐量
垃圾回收器总结
怎么选择垃圾回收器?
- 优先调整堆的大小让JVM自适应完成
- 内存小于100M,选择串行
- 单核、单机程序,没有停顿时间要求,串行
- 多CPU、高吞吐量,允许停顿时间超过1s,选择并行或者JVM自己选择
- 多CPU、低停顿、快速响应,并发收集器G1(大多数互联网是G1)
GC日志分析
-XX:+PrintGC 输出日志信息
[GC (Allocation Failure) 42795K->40696K(95232K), 0.0567535 secs]
[Full GC (Ergonomics) 40696K->40652K(135680K), 0.0164750 secs]
[GC (Allocation Failure) 81564K->80652K(136192K), 0.0503108 secs]
[Full GC (Ergonomics) 80652K->80648K(176640K), 0.0236996 secs]
其中 GC FullGC表示GC的类型,GC只在新生代上进行,Full GC表示永生代、新生代、老年代
Allocation Failure:GC发生原因
42795K->40696K:堆在GC前的大小和后的大小
95232K:现在堆大小
0.0567535 secs:GC持续时间
-XX:+PrintGCDetails 输出日志详细信息
[GC (Allocation Failure) [PSYoungGen: 42795K->696K(53760K)] 42795K->40704K(95232K), 0.0610055 secs] [Times: user=0.01 sys=0.03, real=0.06 secs]
[Full GC (Ergonomics) [PSYoungGen: 696K->0K(53760K)] [ParOldGen: 40008K->40652K(81920K)] 40704K->40652K(135680K), [Metaspace: 3440K->3440K(1056768K)], 0.0849122 secs] [Times: user=0.05 sys=0.00, real=0.09 secs]
[GC (Allocation Failure) [PSYoungGen: 40911K->0K(54272K)] 81564K->80652K(136192K), 0.0633675 secs] [Times: user=0.06 sys=0.06, real=0.06 secs]
[Full GC (Ergonomics) [PSYoungGen: 0K->0K(54272K)] [ParOldGen: 80652K->80648K(122880K)] 80652K->80648K(177152K), [Metaspace: 3440K->3440K(1056768K)], 0.0273931 secs] [Times: user=0.06 sys=0.00, real=0.03 secs]
PSYoungGen 696K->0K:使用parallel Scavenge 并行垃圾回收器的新生代GC前后的大小
ParOldGen: 40008K->40652K:使用parallel Old 并行垃圾回收器的老年代GC前后的大小
Metaspace: 3440K->3440K : 元数据区GC前后大小的变化
[Times: user=0.06 垃圾收集器花费的所有CPU时间 sys=0.00 花费在等待系统调用或系统事件的时间, real=0.03 secs GC从开始到结束的时间,包括其他进行占用时间片的实际时间]
-XX:+PrintGCTimeStamps 输出GC时间戳
-XX:+PrintGCDateStamps 输出GC时间戳
-Xloggc:…/logs/gc.log 日志文件输出路径,可通过easyGC 查看
package chapter17;
public class GCLogTest {
private static final int _1MB = 1024 * 1024;
public static void testAllocation(){
byte[] allocation1,allocation2,allocation3, allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB];
}
public static void main(String[] args) {
testAllocation();
}
}
参数:-Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:+PrintGCDetails
[GC (Allocation Failure) [DefNew: 8190K->628K(9216K), 0.0093693 secs] 8190K->6772K(19456K), 0.0094574 secs] [Times: user=0.00 sys=0.02, real=0.01 secs]
Heap
def new generation total 9216K(8196+1024), used 4806K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
from space 1024K, 61% used [0x00000000ff500000, 0x00000000ff59d150, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
Metaspace used 3426K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 372K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0
垃圾回收器的新发展
Shenandoah GC 红帽 降低低停顿,吞吐量高
令人震惊的ZGC
- 尽可能对吞吐量影响不大的情况下,将垃圾回收停顿的时间控制在10ms以内
- 基于region,并发的标记-压缩算法,以低延迟为首要目标
- 并发标记-并发预备重分配-并发重分配-并发映射