垃圾回收器
1. 垃圾回收器的组合关系
垃圾回收器是垃圾回收算法的具体实现。由于垃圾回收器分为年轻代和老年代,除了G1之外其他垃圾回收器必须成对组合进行使用。 这里主要研究的是Serial+SerialOld垃圾回收器、ParNew+CMS(Concurrent Mark Sweep)垃圾回收器、Parallel Scavenge+Parallel Old垃圾回收器,G1垃圾回收器(既可用于年轻代也可用于老年代)
2. 年轻代-Serial垃圾回收器
Serial是是一种单线程串行回收年轻代的垃圾回收器。
2.1 回收年代和算法
年轻代, 采用复制算法
2.2 优点
单CPU处理器下吞吐量非常出色
2.3 缺点
多CPU下吞吐量不如其他垃圾回收器,堆如果偏大会让用户线程处于长时间的等待
2.4 适用场景
Java编写的客户端程序或者硬件配置有限的场景
3. 老年代-SerialOld垃圾回收器
SerialOld是Serial垃圾回收器的老年代版本,采用单线程串行回收-XX:+UseSerialGC
新生代、老年代都使用串行回收器。
3.1 回收年代和算法
老年代, 采用标记-整理算法
3.2 优点
单CPU处理器下吞吐量非常出色
3.3 缺点
多CPU下吞吐量不如其他垃圾回收器,堆如果偏大会让用户线程处于长时间的等待
3.4 适用场景
与Serial垃圾回收器搭配使用,或者在CMS特殊情况下使用
3.5 查看当前使用的垃圾回收器
修改JVM参数为-XX:+UseSerialGC
,重新启动。在arthas中,使用dashboard -i 1
查看,可以通过使用的算法间接了解使用的垃圾回收器:
4. 年轻代-ParNew垃圾回收器
ParNew垃圾回收器本质上是对Serial在多CPU下的优化,使用多线程进行垃圾回收-XX:+UseParNewGC
新生代使用ParNew回收器,老年代使用串行回收器。
4.1 回收年代和算法
年轻代, 采用复制算法
4.2 优点
多CPU处理器下停顿时间较短
4.3 缺点
吞吐量和停顿都时间不如G1,所以在JDK9之后不建议使用
4.4 适用场景
JDK8及之前的版本中,与CMS老年代垃圾回收器搭配使用
4.5 查看当前使用的垃圾回收器
修改JVM参数为-XX:+UseParNewGC
,重新启动: 官方已经警告不要使用ParNew垃圾回收器和老年代使用串行回收器了,使用起来底层会有问题,未来会移除。
5. 老年代- CMS垃圾回收器
CMS垃圾回收器关注的是系统的暂停时间,允许用户线程和垃圾回收线程在某些步骤中同时执行,减少了用户线程的等待时间。参数:-XX:+UseConcMarkSweepGC
5.1 回收年代和算法
老年代, 采用标记清除算法
5.2 优点
系统由于垃圾回收出现的停顿时间较短,用户体验好
5.3 缺点
1、内存碎片问题(标记清除算法通病)
2、退化问题(多线程变成单线程清理)
3、浮动垃圾问题(清理过程中有些垃圾会清理不掉)
5.4 适用场景
大型的互联网系统中用户请求数据量大、频率高的场景比如订单接口、商品接口等
5.5 CMS执行步骤
- 初始标记,用极短的时间标记出GC Roots能直接关联到的对象。
- 并发标记, 标记所有的对象,用户线程不需要暂停。
- 重新标记,由于并发标记阶段有些对象会发生了变化,存在错标、漏标等情况,需要重新标记。
- 并发清理,清理死亡的对象,用户线程不需要暂停。
可以看到只有两个阶段会发生短暂的STW: 初始标记、重新标记。
5.6 CMS垃圾回收器存在的问题
- CMS使用了标记-清除算法,在垃圾收集结束之后会出现大量的内存碎片,CMS会在Full GC时进行碎片的整理。 这样会导致用户线程暂停,可以使用
-XX:CMSFullGCsBeforeCompaction
=N参数(默认0)调整N次Full GC之 后再整理。 - 无法处理在并发清理过程中产生的“浮动垃圾”,不能做到完全的垃圾回收(也就是在并发清理阶段,用户线程由于也可以运行,在这期间用户线程可能会创建对象然后又不用了,这就是浮动垃圾,只能在下一次GC的时候处理)。
- 如果老年代内存不足无法分配对象,CMS就会退化成Serial Old单线程回收老年代。
5.5 查看当前使用的垃圾回收器
修改JVM参数为-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
,重新启动: 在arthas里面查看:
6. 年轻代-Parallel Scavenge垃圾回收器
Parallel Scavenge是JDK8默认的年轻代垃圾回收器,多线程并行回收,关注的是系统的吞吐量。具备自动调整堆内存大小的特点。 Parallel Scavenge允许手动设置最大暂停时间和吞吐量。Oracle官方建议在使用这个组合时,不要设置堆内存的最大值,垃圾回收器会根据最大暂停时间和吞吐量自动调整内存大小。
6.1 回收年代和算法
年轻代, 采用复制算法
6.2 优点
吞吐量高,而且手动可控。为了提高吞吐量,虚拟机会动态调整堆的参数
6.3 缺点
不能保证单次的停顿时间
6.4 适用场景
后台任务,不需要与用户交互,并且容易产生大量的对象。比如:大数据的处理,大文件导出
6.5 配置Parallel Scavenge垃圾回收器
- 最大暂停时间
-XX:MaxGCPauseMillis
=n,设置每次垃圾回收时的最大停顿毫秒数 - 最大吞吐量
-XX:GCTimeRatio
=n,设置吞吐量为n(用户线程执行时间 = n/n+1) - 自动调整内存大小
-XX:+UseAdaptiveSizePolicy
设置可以让垃圾回收器根据吞吐量和最大停顿的毫秒数自动调整内存大小, 默认是开启的。
7. 老年代-Parallel Old垃圾回收器
Parallel Old是为Parallel Scavenge收集器设计的老年代版本,利用多线程并发收集。 参数: -XX:+UseParallelGC
或-XX:+UseParallelOldGC
可以使用Parallel Scavenge + Parallel Old这种组合。
7.1 回收年代和算法
老年代, 采用标记-整理算法
7.2 优点
并发收集,在多核CPU下效率较高
7.3 缺点
暂停时间会比较长
7.4 适用场景
与Parallel Scavenge配套使用
7.5 查看当前使用的垃圾回收器
删除JVM参数,使用默认垃圾回收器,启动程序, 并使用arthas观察
使用
-XX:+PrintCommandLineFlags
打印当前JVM参数: 如果指定
-XX:PrintFlagsFinal
可以打印程序中的JVM参数: 搜索MaxGCPauseMillis参数,可见默认是Int的最大值:
搜索GCTimeRatio参数,可见默认是吞吐率为99%:
如果删除堆大小等相关参数, 并添加
-XX:MaxGCPauseMillis=10
,也就是最大暂停时间为1ms,垃圾回收器会怎么设置堆大小的呢? 所以
MaxGCPauseMillis
参数并不是调整越小越好,这时的堆空间使用并不高,而且年轻代才70M,原因就是JVM管理的内存空间越小,所需要的开销也就越小,越能达成设置的参数目标。