文章预习:JVM性能优化(二)垃圾回收算法详解
一、简介
G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方计划在jdk9中将G1变成默认的垃圾收集器,以替代CMS
G1的设计 原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成调优:
- 第一步:开启G1垃圾收集器
- 第二步:设置堆的最大内存
- 第三步:设置最大的停顿时间
G1中提供了三种模式垃圾回收模式,Young GC、Mixed GC 和 Full GC ,在不同的条件下被触发。
二、原理
G1垃圾收集器相比于其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域
这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。
在G1划分区域中,年轻代垃圾收集器依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间,G1收集器通过将对象从一个区域复制到另一个区域,完成了清理工作。
这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了,
在G1中,有一种特殊的区域,叫Humongous区域。
- 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象
- 这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。
- 为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象,如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区存储,为了能找到连续的H区,有时候不得不启动 Full GC
三、Young GC
Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。
- Eden 空间的数据移动到Survivor 空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间
- Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中
- 最终Eden空间的数据为空,GC停止工作,应用线程继续执行
3.1、Remembered Set(已记忆集合)
在GC年轻代的对象时,我们如何找到年轻代中对象的根对象呢?
根对象可能是在年轻代中,也可以在老年代中,那么老年代的所有对象都是根吗?
如果全量扫描老年代,那么这样扫描下来会耗费大量的时间
于是,G1引进了Rset的概念,它的全称是 Remembered Set,其作用是跟踪执行某个堆内的对象引用
每个Region初始化时,会初始化一个remembered set(已记忆集合),这个翻译有点拗口,以下简称RSet,该集合用来记录并跟踪其它Region指向该Region中对象的引用,每个Region默认按照512Kb划分成多个Card,所以RSet需要记录的东西应该是 xx Region的 xx Card。
3.2、Mixed GC
当越来越多的对象晋升到老年代Old Region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,既Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择那些Old region 进行收集,从而可以对垃圾回收的耗时时间进行控制,也要注意的是Mixed GC并不是Full GC
Mixed GC什么时候出发?由参数 -XX:InitiatingHeapOccupancyPercent=n
决定。默认:45%,该参数的意思是:当老年代大小占用整个堆大小百分比达到该阈值时触发。
它的GC步骤分两步:
1. 全局并发标记(global concurrent marking)
2. 拷贝存活对象(evacuation)
3.2.1 全局并发标记
全局并发标记,执行过程分为五个步骤:
-
**初始标记(initial mark ,STW):**标记从根节点直接可达的对象,这个阶段会执行一次年轻代GC,会产生全局停顿。
-
根区域扫描(root region scan):
- G1 GC在初始标记的存活区扫描对老年代的引用,并标记被引用的对象
- 该阶段与应用程序(非STW)同时运行,并且只有完成该阶段后,才能开始下一次STW年轻代垃圾回收。
-
并发标记(Concurrent Marking): G1 GC在整个堆中查找可访问的(存活的)对象,该阶段与应用程序同时运行,可以被STW年轻代垃圾回收中断
-
重新标记(Remark,STW): 该阶段是STW回收,因为程序在运行,针对上一次的标记进行修改。
-
清楚垃圾(Cleanup,STW): 轻点和重置标记状态,该阶段会STW,这个阶段并不会实际上去做垃圾的收集,等待evacuation阶段来回收
3.2.2 拷贝存活对象
Evacuation 阶段是全暂停的,该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理。
四、G1 收集器相关参数
-
-XX:+UseG1GC: 使用G1垃圾收集器
-
-XX:MaxGCPauseMillis: 设置期望达到最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是200毫秒
-
-XX:G1HeapRegionSize=n:
- 设置的G1区域的大小,值是2的幂,范围是 1MB 到 32MB之间,目标是根据最小的Java堆大小划分出约2048个区域
- 默认 是堆内存的1/2000
-
-XX:ParallelGCThreads=n: 设置STW 工作线程数的值,将 n的值设置为逻辑处理器的数量,n的值与逻辑处理器的数量相同,最多为8
-
-XX:ConcGCThreads=n: 设置并行标记的线程数,将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右
-
**-XX:InitiatingHeapOccupancyPercent=n: **设置出发标记周期的java堆占用率阈值,默认占用率是这个Java堆的45%
五、测试
5.1 测试代码:
public class TestGC {
// 实现:不断的产生新的数据(对象),随机的去废弃对象(垃圾)
public static void main(String[] args) throws Exception {
List<Object> list = new ArrayList<>();
while (true){
int sleep = new Random().nextInt(100);
if(System.currentTimeMillis() % 2 ==0){
//当前的时间戳,是偶数
list.clear();
}else{
//向List中添加1000个对象
for (int i = 0; i < 10000; i++) {
Properties properties = new Properties();
properties.put("key_"+i,"value_"+System.currentTimeMillis()+i);
list.add(properties);
}
}
Thread.sleep(sleep);
}
}
}
5.2 测试参数:
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+PrintGCDetails -Xmx256m
5.3 日志输出:
[GC pause (G1 Evacuation Pause) (young), 0.0027884 secs]
[Parallel Time: 2.2 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 542.1, Avg: 542.1, Max: 542.2, Diff: 0.1]
# 扫描根节点
[Ext Root Scanning (ms): Min: 0.1, Avg: 0.3, Max: 0.5, Diff: 0.4, Sum: 2.1]
# 更新RS区域所消耗的时间
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
# 对象拷贝
[Object Copy (ms): Min: 1.6, Avg: 1.8, Max: 1.9, Diff: 0.4, Sum: 14.6]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
[Termination Attempts: Min: 1, Avg: 5.5, Max: 7, Diff: 6, Sum: 44]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[GC Worker Total (ms): Min: 2.1, Avg: 2.1, Max: 2.2, Diff: 0.1, Sum: 17.2]
[GC Worker End (ms): Min: 544.2, Avg: 544.2, Max: 544.2, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms] # 清空CardTable
[Other: 0.5 ms]
[Choose CSet: 0.0 ms] # 选取 CSet
[Ref Proc: 0.4 ms] # 弱引用、软引用的处理耗时
[Ref Enq: 0.0 ms] # 弱引用、软引用的入队耗时
[Redirty Cards: 0.0 ms]
[Humongous Register: 0.0 ms] # 大对象区域注册耗时
[Humongous Reclaim: 0.0 ms] # 大对象区域回收耗时
[Free CSet: 0.0 ms]
[Eden: 12.0M(12.0M)->0.0B(13.0M) Survivors: 0.0B->2048.0K Heap: 12.0M(252.0M)->3282.5K(252.0M)] # 年轻代的大小统计
[Times: user=0.00 sys=0.00, real=0.00 secs]
五、对于G1垃圾回收器优化建议
-
年轻代大小:
- 避免使用 -Xmn选项或 -XX:NewRatio 等其他相关选项显示设置年轻代大小
- 固定年轻代的大小会覆盖暂停时间目标
-
暂停时间目标不要太过严苛
- G1 GC 的吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间
- 评估G1 GC的吞吐量时,暂停时间目标不要太严苛,目标太过严苛表示你愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量
六、可视化GC日志分析工具
6.1 GC日志输出参数
前面通过 -XX:PrintGCDetails
可以对GC日志进行打印,我们就可以在控制台查看,这样虽然可以查看GC的信息,但是并不直观,可以借助于第三方的GC日志分析工具进行查看
在日志打印输出设计到的参数如下:
- -XX:+PrintGC:输出GC日志
- -XX:+PrintGCDetails:输出GC的详细日志
- -XX:+PrintGCTimeStamps:输出GC的时间戳(以基准的时间的形式)
- -XX:+PrintGCDateStamps:输出GC的时间戳(以日期的形式,如: 2020-05-04T21:25.234+0800)
- -XX:+PrintHeapAtGC:在进行GC的前后打印出堆的信息
- -Xloggc:…/logs/gc.log 日志文件的输出路径
测试参数:
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E://test//gc.log
运行main 方法后,我们就可以在 E://test//gc.log
下发现一个 gc.log 的日志文件:
6.2 GC Easy 可视化工具
GC Easy是一款在线可视化工具,易用、功能强大,可以通过GC日志分析进行内存泄露检测、GC暂停原因分析、JVM配置建议优化等功能,而且是可以免费使用的
网站地址:http://gceasy.io
上传gc.log后,点击Analyze,就可以查看日志报告了
JVM的各个分代区域分配的内存及使用峰值的内存
关键性能指标: 吞吐量及GC暂停平均时间、最大时间、各个时间段的比例
- Throughput表示的是吞吐量
- Latency表示响应时间
- Avg Pause GC Time 平均GC时间
- Max Pause GC TIme 最大GC时间
第一部分是Heap after GC,GC后堆的内存图,堆是用来存储对象的
第二部分是Heap before GC,这是GC前堆的使用率,可以看出随着程序的运行,堆使用率越来越高,堆被对象占用的内存越来越大。
第三部分是GC Duration Time,就是GC持续时间。从图中可以看到,发生Full GC的时间持续的比较短,而Young GC持续的时间比较长。图中横坐标表示GC发生的时间段,纵坐标表示的是GC持续时间。
表示的是GC回收掉的垃圾对象的内存大小。
GC Statistics
**Reclaimed Bytes(gb):**表示的是堆内存中Minor GC和Full GC回收垃圾对象的内存。
GC cumulative Time(ms):总计GC时间,包括Minor GC和Full GC,时间单位为ms。
GC Average Time(ms):GC平均时间,包括了Minor GC和Full GC。
总GC统计
MinorGC的统计
FullGC的统计
GC暂停程序的统计
七、结束语
到这里G1垃圾回收器就讲完了,感兴趣的小伙伴记得点赞关注,大家加油!