1 影响GC性能的因素
有两个重要的因素影响GC的性能,分别是可用的总内存大小以及年轻代占用内存的比例。
1.1 总的堆空间
那么,为什么说影响GC性能最重要的因素是可用的总内存空间大小?因为,JVM在年轻代或者老年代的堆空间被填满后触发GC的收集操作,而理论上吞吐率与可用的内存空间大小成反比,因此GC需要消耗更多的时间去遍历内存区的对象,这导致GC的暂停延长了。
虽然所有的GC类型都使用了相似的实现机制,但也有很多区别,后续将逐一论述,需要具体问题具体分析。
1.1.1 分代堆空间的设置
有很多设置选项可以影响年轻代或者老年代的堆空间大小,如下图1所示,下面对该图进行详细的描述。
图1
JVM在初始化启动的时候会预定全部的堆空间大小,如上图1所示的Total Size,其对应最大的堆空间大小,可以通过命令行参数-Xmx指定,而最小的堆空间大小,可以通过命令行参数-Xms 指定,当-Xms设定的值小于-Xmx设定的值,也就是堆空间大小的下限值小于上限值时,其预定的总内存空间大小并没有全部提交给虚拟机使用,如上编文章中所述,JVM通过启发式的分析方式自动伸缩或者增长堆空间大小,所以保留上图1中的Virtual部分空间未提交给JVM使用,在年轻代与老年代中都保留有Virtual部分的内存区域,当JVM需要使用时候,可以扩展并使用Virtual部分的内存区域。
此外,另一个重要的命令行参数–XX:NewRatio是老年代与年轻代的内存区域占用的比率,如上图1所示,其比率值=老年代的堆空间大小/年轻代的堆空间大小(其中包括Virtual部分的内存区域)
1.1.2 堆空间的默认值
JVM通过启发式的行为分析方法在每次执行GC操作的时候,对堆空间大小进行增长或者伸缩,以此保证给存活对象保留适当的空闲的堆空间大小,其大小的范围可以通过两个参数指定:-XX:MinHeapFreeRatio=<minimum>和-XX:MaxHeapFreeRatio=<maximum>,minimum与maximum是以百分比值的形式设置,前者的意义是最小的空闲堆空间大小与总堆空间大小的比率(默认值是40,即比率是40%),后者的意义是最大的空闲堆空间大小与总堆空间大小的比率(默认值是70,即比率是70%),总的空闲堆空间大小的范围是大于–Xms<min>命令行参数设定的值,小于–Xmx<max>命令行参数设定的值。该设置选项应用于年轻代或者老年代,也就是当年轻代发生GC的时候、当老年代发生GC的时候都可使用。
根据以上的选项设置,默认的情况下,当年轻代或者老年代的发生GC的时候,如果空闲的堆空间大小占用比例低于40%,则JVM会向Virtual区域扩展,以保持40%的比例水平,当然,扩展后的总堆空间大小不能大于该区域允许的最大值。如果空闲堆空间大小占用比例大于70%,则JVM会向Virtual区域释放堆空间,以保持70%的比例水平,当然,收缩后的总堆空间大小不能小于该区域的允许的最小值。
由前面章节的论述可知,JVM使用启发式的分析方法以及用户所指定的命令行参数的配置,综合性地对堆空间的分配进行动态的伸缩,有些上界值或者下界值是系统的默认值,例如,理论上,在32位操作系统以及64位操作系统中,最大的堆空间大小都受限于操作系统自身的物理内存空间的大小。
从以上的分析可知,提供以下几点常用的有关堆空间大小分配与设置的指导建议:
Ø 除非有因GC暂停而引起的问题,否则尽量给JVM分配更多的内存空间,一般情况下,系统分配的默认值太小
Ø 把-Xms 与-Xmx两个命令行参数的值设置相等,这有利于JVM节省一点资源,因为JVM不必要再消耗资源去计算或决策最小值以及最大值
Ø 一般情况下,当系统的处理器的数量增长,内存空间的大小也必须增长,因为这有利于JVM保证能有足够的堆内存空间去并行化其操作
1.1.3 最小化堆空间
处理器和内存资源是操作系统最重要的资源,所以为了节省或者合理分配系统的资源,有些应用场景需要最小化JVM的运行时的堆空间大小,例如嵌入式客户端应用,可以降低-XX:MaxHeapFreeRatio的值到10%,由于已经限制了最大值,则JVM自动也降低了最小值的限制,这样的设置对性能的影响非常小。然而,最佳的方法是具体问题具体分析,因为不同的应用场景,需求通常有很大的不同,可以逐步将配置调整到最佳的设置。
此外,也可以通过-XX:-ShrinkHeapInSteps命令行参数设置,当发生GC时候,JVM会立刻一步到位地将最大空闲堆空间的值降低到-XX:MaxHeapFreeRatio设定的值,这样的设置虽然能迅速调整,但可能会导致性能降低。默认地,JVM会逐步地调整到指定的目标值,通常会经过几次的GC操作周期才能调整到目标值,这样的机制也是为了最低限度地不影响应用的正常运作。
1.2 年轻代
另一个影响GC性能的重要因素是年轻代占用总堆空间的比例,理论上,年轻代占用的堆空间比例越大,则minor collection(年轻代GC专用操作)发生的次数就越少。然而,对于一个大小固定的总堆空间,年轻代占用的比例越大,则意味着老年代占用的比例就越小,因此,老年代占用的空间变小导致major collection(老年代GC专用操作)发生次数越多,所以,最佳的堆空间分配比例需要根据实际环境中应用的运行情况而决定的,例如,Java对象的生命存活时间在堆空间中的分布情况。
1.2.1 年轻代堆空间的设置
如前面章节所述,默认地,年轻代的堆空间占用比例是由命令行参数-XX:NewRatio设定。例如,-XX:NewRatio=3,表示的意义是年轻代与老年代的堆空间大小比例是1:3,也就是老年代占用总堆空间的比例是3/4,而年轻代占用总堆空间的比例是1/4。
此外,-XX:NewSize命令行参数设定了年轻代的最小占用堆空间的大小,-XX:MaxNewSize命令行参数设定了年轻代的最大占用堆空间的大小,类似于-Xms和-Xmx这两个命令行参数的设定(JVM堆空间的总大小的范围),这样的设定的好处是能固定其空间大小。
1.2.2 存活区堆空间的设置
JVM提供命令行参数-XX:SurvivorRatio设定存活区的比率,在一般情况下,这个设置对性能影响不大。例如,-XX:SurvivorRatio=6,表示Eden区堆空间与其中一个Survivor区堆空间的比率是1:6,也就是每个Survivor区堆空间的大小是Eden区堆空间的1/6,因为年轻代有两个Survivor区,所以每个Survivor区堆空间的大小是年轻代堆空间的1/8。
如果Survivor区堆空间设置太小,则很容易被填满,从而很多存活对象都被直接复制到老年代堆区,如果Survivor区堆空间设置太大,则总是填不满,从而浪费部分内存空间,所以,在每次发生GC的时候,JVM都会选择会一个临界值,该值规定一个存活对象被复制到老年代区之前最多能在Survivor区之间被复制的次数,该临界值可以保证Survivor区总是处于半填满的状态。用户可以使用前面论述的命令行参数-Xlog:gc打印GC操作的日志,其中可以观察到该临界值,也可以更加详细地观察到堆区中存活对象生命的分布情况。
下面的表格是JVM提供的一些默认值:
命令行参数 | 默认值 |
-XX:NewRatio | 2 |
-XX:NewSize | 1310 MB |
-XX:MaxNewSize | not limited(无限制) |
-XX:SurvivorRatio | 8 |
由以上表格可知,年轻代的堆空间大小是由JVM的总堆空间大小以及-XX:NewRatio命令行参数值共同计算所得,-XX:MaxNewSize命令行参数的默认值是无限制,其意思是无指定则由JVM计算所得,若指定则由指定值决定。
下面提供一些常用的堆空间调优(主要是针对server类的应用)指导:
Ø 首先需要确定能提供给JVM的最大堆空间大小,当然不能大于系统的物理内存,然后通过监控与统计分析的手段,在压力测试环境或者在其他可用的环境中不断调整年轻代堆空间的占比,最终确定最恰当的数值
Ø 确定总堆空间大小之后,确定年轻代堆空间大小与老年代堆空间的比例,并需要保证老年代堆区拥有足够多的空间去保存存活对象,而且还需要保留10%~20%或者更多占比的冗余空闲空间,以防止访问量突然增多的应用场景
Ø 为年轻代分配尽可能多的堆空间,并且当系统处理器数量增加的时候,年轻代的堆空间大小也需要跟随增加,目的是更好地实现并行化操作
(未完待续)