22 如何优化垃圾回收机制

118 阅读6分钟

大家好,我是小水珠。

我们知道,在Java开发中,开发人员是无需多度关注对象的回收与释放的,JVM垃圾回收机制可以减轻不少工作量。但完全交由JVM回收对象,也会增加回收性能的不确定性。在一些特殊的业务场景下,不适合的垃圾回收算法以及策略,都有可能导致系统性能下降。

面对不同的业务场景,垃圾回收的调优策略也不一样。例如,在对内存要求苛刻的情况下,需要提高对象的回收效率;在CPU使用率高的情况下,需要降低高并发时垃圾回收的频率。可以说,垃圾回收的调优是一项必备技能。

这讲我们就把这项技能的学习进行拆分,看看回收(GC)的算法有哪些,体现GC算法好坏的指标有哪些,又如何根据自己的业务场景对GC策略进行调优?

一 垃圾回收机制

1.回收发生在哪里?

垃圾回收的重点就是管制堆和方法区中的内存,堆中的回收主要是对象的回收,方法区的回收主要是废弃常量和无用的类的回收。

2.对象在什么时候可以被回收?

  • 引用计数算法

这种算法是通过一个对象的引用计数器来判断该对象是否被引用了。当对象的引用计数器的值为0时,就说明该对象不再被引用,可以被回收。但它存在着对象之前相互循环引用的问题。

  • 可达性分析算法

GC Roots是该算法的基础,在垃圾回收时,会从这些GC Roots开始向下搜索,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。目前HotSpot虚拟机采用的就是这种算法。

以上两种算法都是通过引用来判断对象是否可以被回收。在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为了以下四种:

22-引用类型.jpg

3.如何回收这些对象?

  • 自动性

Java提供了一个系统级的线程来跟踪每一块分配出去的内存空间,当JVM处于空闲循环时,垃圾收集器线程会自动检查每一块分配出去的内存空间,然后自动回收每一块空闲的内存块。

  • 不可预期性

一旦一个对象没有被引用了,该对象是否被立刻回收呢?答案是不可预期的。

二 GC算法

JVM提供了不同的回收算法来实现这一套回收机制,通常垃圾收集器的回收算法可以分为以下几种:

22-垃圾回收算法.jpg

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。JDK1.7 update14之后HotSpot虚拟机所有的回收器整理如下(以下为服务端垃圾收集器):

22-回收器类型.jpg

其实在JVM规范中并没有明确GC的运作方式,各个厂商可以采用不同的方式实现来及收集器。我们可以通过JVM工具查询当前JVM使用的垃圾收集器类型,首先通过ps命令查询出进程ID,在通过jmap -head ID查询出JVM的配置信息,其中就包括垃圾收集器的设置类型。

22-垃圾器类型查看.png

三 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

打印后日志为:

22-gc日志.png

上图是运行很短时间的GC日志,如果是长时间的GC日志,我们我们很难通过文本形式去查看整体的GC性能。此时,我们可以通过GCViewer工具打开日志文件,图形化界面查看整体的GC性能,如下图所示:

22-gcviewer1.jpg 22-gcviewer2.png

五 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的一些性能配置参数。如果一定要改,那就必须基于大量的测试结果或线上的具体性能来进行调整。