JVM 参数调整对 sortx 的影响

Java虚拟机参数的配置有时候会对SPL的性能产生很大影响,本文从排序函数sortx的使用出发,探索如何通过优化JVM参数配置来提升性能。对分析过程不感兴趣的同学可以直接阅读结论章节。

1 内存构成

年老代

年轻代


Eden

S1

S2

如图,Java虚拟机的内存,也就是常说的堆,主要分为年轻代和年老代两个部分。年轻代存放生命周期很短的对象,年老代存放长期存活的对象。例如,经过几次垃圾回收之后,一个对象仍然存活,那么这个对象就会从年轻代进入年老代。

年轻代又分为3个区:EdenSurvivor 1Survivor 2(以下简称EdenS1S2)。Eden存放新对象,S1S2交替使用,用来存放垃圾回收时存活的对象。

2 回收策略

Java虚拟机的垃圾回收策略很复杂,这里我们只关心最影响性能的几种情况。

Java的垃圾回收分为两种:Full GCminor GC,前者是对年老代的回收,一般会很慢,后者是针对年轻代的回收,这个很快。我们调优的目标就是尽量避免频繁的Full GC。那么什么情况会触发Full GC呢?上面说了,新对象会存放在Eden区,而当Eden区满了的时候就会触发minor GC,此时Eden区的垃圾对象会被清除,而存活下来的有用对象则会往S1S2区复制,从而完成一次回收过程。在这个过程中,如果S1S2区装不下Eden里幸存的对象了,就会把幸存的对象保存到年老代,而如果年老代也没有空间了,就会触发Full GC

简述就是,Survivor区装不下年轻代的幸存对象时,会造成年老代的增长,随着年老代不断增长,Full GC必然会被触发。

3 sortx 时的内存使用

我们知道sortx函数有个参数nn是缓冲区条数,表示每次从游标中取出来的记录条数。那么n取什么值最合适呢?

显然n不能取值太大,否则会造成内存溢出,根本无法使用。

那么再不断尝试取小点,经过几次尝试后,会发现可以用了,不会溢出了。但这时不一定是最优取值,如果n取值占用的内存大于Eden,且正好把年老代占用的差不多了,则每次取出的数据都会送进年老代,等到下一次取数的时候,就触发Full GC,而这种频繁触发是最耗时的。

所以,从Java的分配和回收策略来看,最理想的情况是,n的取值占用的内存,略小于Eden的大小。这是因为sortx这类运算的特点是“取出即用,用完就扔”,很少有对象会常驻内存。

那么如何知道Eden区的大小然后估算n呢?

4 内存分配

在 IDE 的启动配置里可以看到,参数 -Xmx 用来指定分配给 java 的堆内存的大小。例如 -Xmx10g 就表示分配了 10G 内存给 Java。那么 Java 如何把这 10G 分配给这几个区呢?

默认的情况下,各区比例大概是这样的:

年轻代 : 年老代 = 3 : 7

Eden : S1 : S2 = 8 : 1 : 1

      有了比例,很容易算出来各区大小,年老代占 7G,年轻代占 3G,其中 Eden 占 2.4G,S1 和 S2 各占 0.3G。据说这个默认比例是 jdk 的开发人员统计得出的,认为在大部分应用中,幸存对象占全部对象的 1/10。然而这个比例不一定适合所有情形,至少在 sortx 时就不太合适。

5 结论

n的取值原则:使用 sortx 时,在不溢出的前提下,n 的取值并不是越大越好,而是(根据 Eden 大小)估算一个合适值,这个值不引起频繁 Full GC。

验证 n 值是否合适的办法如下:

1 添加参数 -XX:+PrintGC。

2 运行 sortx。

3 观察控制台,如果频繁出现 [Full GC (Allocation Failure)……] 信息则调小 n,重试。

4 如果只出现很多 [GC (Allocation Failure)……] 信息,则认为 n 的值是合适的。

5 如果出现很多 [GC (Allocation Failure)……] 信息的同时,偶尔出现 [Full GC (Allocation Failure)……] 信息,则也可以认为 n 的值是合适的。

6 确保临时文件不能太小。

6 调参

一般来说通过调整n,就基本可以满足sortx的性能了,如果还想进一步追求性能,就需要调参了。默认分配的年轻代只占堆的3/10,而年老代那7/10的内存在sortx这种计算时显得作用不大。如果想调整这个比例,又允许sortx使用一个专门的启动配置,则可以使用参数-XX:NewSize进行调整。

例如,-XX:NewSize=5g,就指定年轻代的大小是5G

提示一下,调整的时候,年老代的空间也不能留的太少,占整个堆的1/5是合适的。这是因为Java的分配策略还有很多复杂情况,比如总空间够但不连续时,仍会直接把对象装入年老代。类似的情况还有一些,不再赘述。

同样地,EdenS1S2的比例也会影响性能,如果想调整这个比例也可以使用参数-XX:SurvivorRatio。例如-XX:SurvivorRatio=1表示Eden:S1:S2 = 1:1:1-XX:SurvivorRatio=8表示Eden:S1:S2 = 8:1:1

一般来说,Survivor区越大,对象进入老年代的概率就越小,所以在做sortx时,倾向于配置为-XX:SurvivorRatio=1

7 其它

参数的配置没有一致的准则,需要根据计算的类型来调整,有时候年老代大一些好(比如大维表需要常驻内存的),有时候则是年轻代大一些好。