大家好,我是小水珠。
我们知道,在Java开发中,开发人员是无需多度关注对象的回收与释放的,JVM垃圾回收机制可以减轻不少工作量。但完全交由JVM回收对象,也会增加回收性能的不确定性。在一些特殊的业务场景下,不适合的垃圾回收算法以及策略,都有可能导致系统性能下降。
面对不同的业务场景,垃圾回收的调优策略也不一样。例如,在对内存要求苛刻的情况下,需要提高对象的回收效率;在CPU使用率高的情况下,需要降低高并发时垃圾回收的频率。可以说,垃圾回收的调优是一项必备技能。
这讲我们就把这项技能的学习进行拆分,看看回收(GC)的算法有哪些,体现GC算法好坏的指标有哪些,又如何根据自己的业务场景对GC策略进行调优?
一 垃圾回收机制
1.回收发生在哪里?
垃圾回收的重点就是管制堆和方法区中的内存,堆中的回收主要是对象的回收,方法区的回收主要是废弃常量和无用的类的回收。
2.对象在什么时候可以被回收?
- 引用计数算法
这种算法是通过一个对象的引用计数器来判断该对象是否被引用了。当对象的引用计数器的值为0时,就说明该对象不再被引用,可以被回收。但它存在着对象之前相互循环引用的问题。
- 可达性分析算法
GC Roots是该算法的基础,在垃圾回收时,会从这些GC Roots开始向下搜索,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。目前HotSpot虚拟机采用的就是这种算法。
以上两种算法都是通过引用来判断对象是否可以被回收。在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为了以下四种:
3.如何回收这些对象?
- 自动性
Java提供了一个系统级的线程来跟踪每一块分配出去的内存空间,当JVM处于空闲循环时,垃圾收集器线程会自动检查每一块分配出去的内存空间,然后自动回收每一块空闲的内存块。
- 不可预期性
一旦一个对象没有被引用了,该对象是否被立刻回收呢?答案是不可预期的。
二 GC算法
JVM提供了不同的回收算法来实现这一套回收机制,通常垃圾收集器的回收算法可以分为以下几种:
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。JDK1.7 update14之后HotSpot虚拟机所有的回收器整理如下(以下为服务端垃圾收集器):
其实在JVM规范中并没有明确GC的运作方式,各个厂商可以采用不同的方式实现来及收集器。我们可以通过JVM工具查询当前JVM使用的垃圾收集器类型,首先通过ps命令查询出进程ID,在通过jmap -head ID查询出JVM的配置信息,其中就包括垃圾收集器的设置类型。
三 GC性能衡量指标
- 吞吐量
这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。
- 停顿时间
指垃圾收集器正在运行时,应用程序的暂停时间。
- 垃圾回收频率
指多久发生一次垃圾回收,通常垃圾回收的频率越低越好。
四 查看&分析GC日志
已知了性能衡量指标,现在我们需要通过工具查询GC相关日志,统计各项指标的信息。首先,我们需要通过JVM参数预先设置GC日志,通常有以下几种JVM参数设置:
-
-XX:+PrintGC 输出GC日志
-
-XX:+PrintGCDetails 输出GC的详细日志
-
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式)
-
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-
-Xloggc: ../logs/gc.log 日志文件的输出路径
这里如下参数来打印日志:
-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc: ../logs/gc.log
打印后日志为:
上图是运行很短时间的GC日志,如果是长时间的GC日志,我们我们很难通过文本形式去查看整体的GC性能。此时,我们可以通过GCViewer工具打开日志文件,图形化界面查看整体的GC性能,如下图所示:
五 GC调优策略
1.降低Minor GC频率
通常情况下,由于新生代空间较小,Eden区很快被填满,就会导致频繁Minor GC,因此我们可以通过增大新生代空间来降低Minor GC的频率。
2.降低Full GC频率
- 减少创建大对象
大对象如果超过年轻代最大对象阈值,会被直接创建在老年代;即使被创建在了年轻代,由于年轻代的内存空间有限,通过Minor GC之后进入到老年代。这种大对象很容易产生较多的Full GC。
- 增大堆内存空间
在堆内存不足的情况下,增大堆内存空间,且设置初始化堆内存为最大堆内存,也可以降低Full GC的频率。
3.选择合适的GC回收器
假如我们有一个需求,要求每次操作的响应时间必须在500ms以内。这个时候我们一般会选择相应速度较快的GC回收器,CMS回收器和G1回收器都是不错的选择。
而当我们的需求对系统的吞吐量有要求时,就选择Parallel Scanvenge回收器来提高系统的吞吐量。
六 总结
今天的内容比较多,最后再强调几个重点。
垃圾收集器的种类很多,我们可以将其分成两种类型,一种是响应速度快,一种是吞吐量高。通常情况下,CMS和G1回收器的响应速度快,Parallel Scavenge回收器的吞吐量高。
在JDK1.8环境下,默认使用的是Parallel Scavenge(年轻代)+ Serial Old(老年代)垃圾收集器,你可以通过中文介绍查询JVM默认配置方法进行查看。
通常情况,JVM是默认来及回优化的,在没有性能衡量标准的前提下,尽量避免修改GC的一些性能配置参数。如果一定要改,那就必须基于大量的测试结果或线上的具体性能来进行调整。