声明:原创文章,转载请注明出处。http://www.jianshu.com/u/e02df63eaa87
1、JVM参数设置
1.1 设置堆内存大小
- 参数:-Xmx:最大堆
-Xms:最小堆 - 含义:堆大小指的是新生代和老生代大小之和。
- 备注:Java应用程序运行时,首先会被分配-Xms指定的内存大小,并且尽可能尝试在这个空间段内运行。当内存无法满足程序时,JVM会向系统申请内存,直到内存大小为-Xmx。如果-Xms指定的内存较小,那么JVM GC的频率要更高。
- 注意:当内存使用量到达-Xms时,会触发Full GC。因此,将-Xms和-Xmx设置相等时,会减少系统初期GC次数和耗时。
1.2 设置新生代
- 参数:-XX:NewSize 新生代初始大小
-XX:MaxNewSize新生代最大值
-Xmn 新生代大小,设置此值等于设置相同的-XX:NewSize 和-XX:MaxNewSize - 备注:设置不同的-XX:NewSize 和-XX:MaxNewSize可能会导致内存震荡。
1.3 设置持久代
- 参数:-XX:PermSize 持久代初始大小
-XX:MaxPermSize持久代最大值 - 含义:持久代(方法区)不属于堆的一部分。持久代的大小直接决定了可以支持多少个类和多少个变量。
1.4 设置线程栈
- 参数:-Xss 线程栈大小
- 含义:在线程中进行局部变量分配,以及函数调用时,都需要在栈中开辟空间。若栈的空间太小,则线程在执行时,或许没有足够的空间分配变量或达不到足够的函数调用深度,导致应用异常退出。若栈的空间太大,开启线程所需的成本会上升,系统所支持的线程数则会减少。
- 备注:如果指定的最大堆的大小,则系统支持最大线程数,与堆的大小有关。如果分配给堆的内存过大,则系统支持的最大线程数则会减小。
1.5 堆的比例分配
- 参数:-XX:SurvivorRatio 用来设置新生代中Eden区和幸存区(From)的比例
** -XX:XX:SurvivorRatio = Eden/From = Eden/To**
-XX:NewRatio设置老年代和新生代的比例 - 含义:-XX:SurvivorRatio默认为8,新生代三部分空间比例8:1:1。-XX:NewRatio默认为2,新生代和老年代比例为1:2。
2、常见调优案例
2.1 将新对象留在新生代
Full GC的成本要远远高于Minor GC,所以尽可能将对象分配在新生代。虽然大多数情况下,JVM会尝试在Eden区分配新对象,由于内存紧张问题,可能不得不将年轻代对象提前向老年代压缩。所以,为应用程序分配一个恰当的新生代大小,可最大限度避免新对象直接进入老年代。一般的,当Survivor空间不够或占用量达到50%时,就会将对象进入老年代,可以通过调整参数-XX:TargetSurvivorRatio提高Survivor区的利用率。
以下代码申请了4MB空间:
public class Eden {
public static void main(String[] args) {
int[] a, b;
a = new int[512 * 1024]; // 2MB
b = new int[512 * 1024]; // 2MB
}
}
使用JVM参数:-XX:+PrintGCDetails -Xms15M -Xmx15M运行,堆内存输出为:
Heap
PSYoungGen total 4608K, used 3325K [0x00000000ffa80000, 0x00000000fff80000, 0x0000000100000000)
eden space 4096K, 81% used [0x00000000ffa80000,0x00000000ffdbf548,0x00000000ffe80000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000ffe80000,0x00000000ffe80000,0x00000000fff00000)
ParOldGen total 10240K, used 2048K [0x00000000ff000000, 0x00000000ffa00000, 0x00000000ffa80000)
object space 10240K, 20% used [0x00000000ff000000,0x00000000ff200010,0x00000000ffa00000)
PSPermGen total 21504K, used 2892K [0x00000000f9e00000, 0x00000000fb300000, 0x00000000ff000000)
object space 21504K, 13% used [0x00000000f9e00000,0x00000000fa0d3060,0x00000000fb300000)
可以看出,一个新对象已经进入了老年代。
使用参数:-XX:+PrintGCDetails -Xms15M -Xmx15M -Xmn8M运行,分配足够大的新生代,GC输出如下:
Heap
PSYoungGen total 7168K, used 5506K [0x00000000ff800000, 0x0000000100000000, 0x0000000100000000)
eden space 6144K, 89% used [0x00000000ff800000,0x00000000ffd60850,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 4096K, used 0K [0x00000000ff200000, 0x00000000ff600000, 0x00000000ff800000)
object space 4096K, 0% used [0x00000000ff200000,0x00000000ff200000,0x00000000ff600000)
PSPermGen total 21504K, used 2944K [0x00000000fa000000, 0x00000000fb500000, 0x00000000ff200000)
object space 21504K, 13% used [0x00000000fa000000,0x00000000fa2e0150,0x00000000fb500000)
2.2 大对象进入老年代
大对象直接出现在新生代可能会扰乱新生代GC,这是因为在新生代尝试分配大对象,可能会导致空间不足,为了能容纳大对象,JVM不得不将新生代中其他年轻的对象移到老年代(或许会移动大量小的年轻对象进人老年代)。
综上,可以将大对象直接分配到老年代,但是许多大对象同时又是非活跃的对象,会对老年代GC产生问题,所以应尽量避免这种情况的发生。
以下代码申请了2MB空间:
public class Eden {
public static void main(String[] args) {
int[] a = new int[512 * 1024]; // 2MB
}
}
由于-XX:PretenureSizeThreshold参数对PS收集器不适用,因此采用ParNew收集器。
使用JVM参数:-XX:+UseParNewGC -XX:+PrintGCDetails -Xms15M -Xmx15M 运行,堆内存输出为:
Heap
par new generation total 4608K, used 3481K [0x00000000f9e00000, 0x00000000fa300000, 0x00000000fa350000)
eden space 4096K, 85% used [0x00000000f9e00000, 0x00000000fa1666e8, 0x00000000fa200000)
from space 512K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa280000)
to space 512K, 0% used [0x00000000fa280000, 0x00000000fa280000, 0x00000000fa300000)
tenured generation total 10240K, used 0K [0x00000000fa350000, 0x00000000fad50000, 0x00000000fae00000)
the space 10240K, 0% used [0x00000000fa350000, 0x00000000fa350000, 0x00000000fa350200, 0x00000000fad50000)
compacting perm gen total 21248K, used 2942K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 13% used [0x00000000fae00000, 0x00000000fb0df9c0, 0x00000000fb0dfa00, 0x00000000fc2c0000)
No shared spaces configured.
可以看到,这个对象基本上占据了整个新生代。
使用参数:-XX:+PrintGCDetails -Xms15M -Xmx15M -XX:PretenureSizeThreshold=1M运行,分配足够大的新生代,GC输出如下:
Heap
par new generation total 4608K, used 1353K [0x00000000f9e00000, 0x00000000fa300000, 0x00000000fa350000)
eden space 4096K, 33% used [0x00000000f9e00000, 0x00000000f9f52560, 0x00000000fa200000)
from space 512K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa280000)
to space 512K, 0% used [0x00000000fa280000, 0x00000000fa280000, 0x00000000fa300000)
tenured generation total 10240K, used 2048K [0x00000000fa350000, 0x00000000fad50000, 0x00000000fae00000)
the space 10240K, 20% used [0x00000000fa350000, 0x00000000fa550010, 0x00000000fa550200, 0x00000000fad50000)
compacting perm gen total 21248K, used 2942K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 13% used [0x00000000fae00000, 0x00000000fb0df9c0, 0x00000000fb0dfa00, 0x00000000fc2c0000)
No shared spaces configured.
可以看到,新生代空间有很大的空闲,数组a已经被分配到了老年代上。
2.3 设置对象进入老年代年龄
可以通过-XX:MaxTenuringThreshold设置对象进入老年代的年龄。若对象在eden区,经过一次GC后,会被移动到幸存区,相应的年龄+1。当对象达到阈值(默认15)后,就会移动到老年代。
以下代码申请了3MB空间:
public class Eden {
public static void main(String[] args) {
byte[] a, b, c;
a = new byte[1024 * 1024]; // 1MB
b = new byte[1024 * 1024]; // 1MB
c = new byte[1024 * 1024]; // 1MB
}
}
使用JVM参数:
-XX:+UseParNewGC -XX:+PrintGCDetails -Xms15M -Xmx15M -XX:SurvivorRatio=3 运行,堆内存输出为:
[GC[ParNew: 2178K->644K(4096K), 0.0034094 secs] 2178K->1668K(14336K), 0.0034551 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
Heap
par new generation total 4096K, used 2922K [0x00000000f9e00000, 0x00000000fa300000, 0x00000000fa350000)
eden space 3072K, 74% used [0x00000000f9e00000, 0x00000000fa039488, 0x00000000fa100000)
from space 1024K, 62% used [0x00000000fa200000, 0x00000000fa2a13a0, 0x00000000fa300000)
to space 1024K, 0% used [0x00000000fa100000, 0x00000000fa100000, 0x00000000fa200000)
tenured generation total 10240K, used 1024K [0x00000000fa350000, 0x00000000fad50000, 0x00000000fae00000)
the space 10240K, 10% used [0x00000000fa350000, 0x00000000fa450010, 0x00000000fa450200, 0x00000000fad50000)
compacting perm gen total 21248K, used 2890K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 13% used [0x00000000fae00000, 0x00000000fb0d2910, 0x00000000fb0d2a00, 0x00000000fc2c0000)
No shared spaces configured.
使用参数:-XX:+UseParNewGC -XX:+PrintGCDetails -Xms15M -Xmx15M -XX:SurvivorRatio=3
-XX:MaxTenuringThreshold=0运行,堆内存输出为:
[GC[ParNew: 2302K->0K(4096K), 0.0026039 secs] 2302K->1665K(14336K), 0.0026645 secs] [Times: user=0.06 sys=0.00, real=0.00 secs]
Heap
par new generation total 4160K, used 2156K [0x00000000f9e00000, 0x00000000fa310000, 0x00000000fa350000)
eden space 3136K, 68% used [0x00000000f9e00000, 0x00000000fa01b210, 0x00000000fa110000)
from space 1024K, 0% used [0x00000000fa110000, 0x00000000fa110000, 0x00000000fa210000)
to space 1024K, 0% used [0x00000000fa210000, 0x00000000fa210000, 0x00000000fa310000)
tenured generation total 10240K, used 1665K [0x00000000fa350000, 0x00000000fad50000, 0x00000000fae00000)
the space 10240K, 16% used [0x00000000fa350000, 0x00000000fa4f0500, 0x00000000fa4f0600, 0x00000000fad50000)
compacting perm gen total 21248K, used 2942K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 13% used [0x00000000fae00000, 0x00000000fb0dfa00, 0x00000000fb0dfa00, 0x00000000fc2c0000)
No shared spaces configured.
2.4 稳定与震荡的堆大小
稳定的堆大小可以减少GC次数,但也会增加每次GC的时间。可以让堆大小在一个区间内震荡,不需要使用大内存时,压缩堆指针,加快GC速度。
-XX:MinHeapFreeRatio:设置堆空间最小空闲比例,默认为40。当堆空间小于此值时,JVM会扩展堆空间。
-XX:MaxHeapFreeRatio:设置堆空间最大空闲比例,默认为70。当堆空间空闲内存大于此值时,JVM会压缩堆空间。
备注:当-Xms和-Xmx相等时,以上两个参数设置无效。
2.5 吞吐量优先
-Xms4G -Xmx4G -Xmn2G -Xss128k -XX:UseParallelGC -XX:ParallelGCThreads=20
2.6 降低停顿案例
-Xms4G -Xmx4G -Xmn2G -Xss128k -XX:UseParNewGC -XX:ConMarkSweepGC
-XX:ParallelGCThreads=20 -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31
目的是,最大限度将对象留在新生代(Minor GC成本远小于Full GC)。
3、调优总结
3.1 响应时间优先
- 对于年轻代应尽可能设置大,极端情况下,年轻代GC的频率也是最小的。
- 对于老年代要减少对象。老年代采用并发收集器。堆大小设置要尽量平衡,若太小,会造成内存碎片、高GC频率等,若太大,需要较长的GC回收时间。
3.2 吞吐量优先
- 对于年轻代应尽可能设置大,对响应时间没有特别邀请,垃圾收集可并行进行。
- 对于老年代设置要小,这样可以尽可能回收掉大部分短期对象,减少中期对象,而老年代存放长期对象。