JVM调优 | 8lovelife's life
0%

JVM调优

JVM到底怎么优化啊?优化什么?为啥要优化。。?

堆内存分配与回收时机

大家都知道JVM中的堆内存是分代的结构,来看看堆内存的开辟与回收的细节

堆内存的分配

  • Eden区:对于新创建的对象,一般都会在此区域,新生对象不见得都会在此区域
  • Survivor区:Young区经过Minor GC,存活下的对象一般都此区,Young区存活的对象不见得都会在此区域
  • Old 区:经受住了多次Minor GC,Full GC存活的对象,都在此区域

堆内存的回收

堆内存在Minor GC 与 Full GC发生的时候回收,那么Minor GC 、Full GC 什么时候发生呢。。

Minor GC

Minor GC发生在Young区,当Eden区满时会触发Minor GC,新生代的对象一般存活的时间很短,Minor GC会很频繁,由于是复制算法所以执行时间较快

Full GC /Major GC

Major GC 亦指 Full GC ,Full GC会对整个堆区进行内存回收。Full GC发生在以下情况

  1. Old区空间不足会触发Full GC
  2. Perm区满后会触发Full GC(设置CMS GC的情况除外)
  3. 执行CMS GC的过程中有对象进入Old区,Old区不足会报 Concurrent Mode Failure 错误,并触发 Full GC
  4. Minor GC时检测之前晋升到Old区平均对象大小(这个平均晋升大小是靠不住的),1。若大于Old区剩余大小,则直接触发Full GC。 2。若小于Old区剩余大小,则查看HandlePromotionFailure是否开启,若开启则会触发Full GC
  5. System.gc()方法建议JVM进行Full GC,使用DisableExplicitGC可禁用Full GC的显示调用

JVM调优

扩大年轻代堆空间

JVM尝试将新生对象放入Eden区,若Eden区空间不够则会将对象提前放入Old区。Minor GC的成本远低于Full GC ,因此可以将对象尽可能保留在年轻代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
JVM 参数:-Xmx20M -Xms20M -XX:+PrintGCDetails

private static void OptimizeOne() {
int M = 1024 * 1024;
byte[] b1 = new byte[2 * M]; // 2M
byte[] b2 = new byte[2 * M];
byte[] b3 = new byte[2 * M];
byte[] b4 = new byte[2 * M];
byte[] b5 = new byte[2 * M];
byte[] b7 = new byte[2 * M];
}

GC Log:
[GC (Allocation Failure) [PSYoungGen: 3890K-512K(6144K)] 3890K-2664K(19968K), 0.0033226 secs] [Times: user=0.01 sys=0.01, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 4772K-512K(6144K)] 6924K-6768K(19968K), 0.0057943 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 4733K-496K(6144K)] 10989K-10864K(19968K), 0.0050538 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 496K-0K(6144K)] [ParOldGen: 10368K-10747K(13824K)] 10864K-10747K(19968K), [Metaspace: 3061K-3061K(1056768K)], 0.0088463 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
Heap
PSYoungGen total 6144K, used 2209K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000)
eden space 5632K, 39% used [0x00000007bf980000,0x00000007bfba84c8,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 13824K, used 10747K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000)
object space 13824K, 77% used [0x00000007bec00000,0x00000007bf67ef40,0x00000007bf980000)
Metaspace used 3117K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 340K, capacity 388K, committed 512K, reserved 1048576K

通过GC Log可以发现Young区的大小为6M ,适当调整年轻代大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
JVM 参数:-Xmx20M -Xms20M -Xmn10M -XX:+PrintGCDetails

private static void OptimizeOne() {
int M = 1024 * 1024;
byte[] b1 = new byte[2 * M]; // 2M
byte[] b2 = new byte[2 * M];
byte[] b3 = new byte[2 * M];
byte[] b4 = new byte[2 * M];
byte[] b5 = new byte[2 * M];
byte[] b7 = new byte[2 * M];
}

GC Log:
[GC (Allocation Failure) [PSYoungGen: 6153K-608K(9216K)] 6153K-4704K(19456K), 0.0059569 secs] [Times: user=0.01 sys=0.01, real=0.00 secs]
[GC (Allocation Failure) --[PSYoungGen: 6991K-6991K(9216K)] 11087K-15191K(19456K), 0.0058702 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 6991K-2560K(9216K)] [ParOldGen: 8200K-8192K(10240K)] 15191K-10752K(19456K), [Metaspace: 3159K-3159K(1056768K)], 0.0070982 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 9216K, used 4995K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 60% used [0x00000007bf600000,0x00000007bfae0c10,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
ParOldGen total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 80% used [0x00000007bec00000,0x00000007bf400168,0x00000007bf600000)
Metaspace used 3187K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 347K, capacity 388K, committed 512K, reserved 1048576K

年轻代不留大对象

JVM将新生的对象放入Yong区,若对象一出生就超级大,针对后续新产生的对象可能会导致Young区小对象频繁的转入Old区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
JVM 参数:-Xmx20M -Xms20M -Xmn10M -XX:+PrintGCDetails

private static void OptimizeTwo() {
int M = 1024 * 1024;
byte[] b1 = new byte[3 * M]; // 3M

}

GC Log:
Heap
PSYoungGen total 6144K, used 4914K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000)
eden space 5632K, 87% used [0x00000007bf980000,0x00000007bfe4c8e8,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
ParOldGen total 13824K, used 0K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000)
object space 13824K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf980000)
Metaspace used 3045K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 334K, capacity 388K, committed 512K, reserved 1048576K


新生对象直接占据了Eden区的87%,将新生的大对象直接放入Old区,有利于提升GC效率,设置对象大小阀值-XX:PretenureSizeThreshold=3M (此设置对 +UseParallelGC 无效)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
JVM 参数:-Xmx20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:PretenureSizeThreshold=3M -XX:+UseParNewGC

private static void OptimizeTwo() {
int M = 1024 * 1024;
byte[] b1 = new byte[3 * M]; // 3M

}

GC Log:
Heap
par new generation total 6144K, used 1947K [0x00000007bec00000, 0x00000007bf2a0000, 0x00000007bf2a0000)
eden space 5504K, 35% used [0x00000007bec00000, 0x00000007bede6e40, 0x00000007bf160000)
from space 640K, 0% used [0x00000007bf160000, 0x00000007bf160000, 0x00000007bf200000)
to space 640K, 0% used [0x00000007bf200000, 0x00000007bf200000, 0x00000007bf2a0000)
tenured generation total 13696K, used 3072K [0x00000007bf2a0000, 0x00000007c0000000, 0x00000007c0000000)
the space 13696K, 22% used [0x00000007bf2a0000, 0x00000007bf5a0010, 0x00000007bf5a0200, 0x00000007c0000000)
Metaspace used 3045K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 334K, capacity 388K, committed 512K, reserved 1048576K

可以发现3M的对象直接放入了Old区

对象的年龄

对象的年龄指经过Minor GC仍存活在Young区的次数,当对象超过指定的年龄后会被移动到Old区,年龄未超过设置的阀值一样可能会移动到Old区,当Survivor区中相同年龄大小的所有对象大小总和超过了Survivor区空间的一半(默认 -XX:TargetSurvivorRatio=50)则会被移动到Old区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
JVM 参数:-Xmx20M -Xms20M -Xmn10M -XX:+PrintGCDetails
private static void OptimizeThree() {
int M = 1024 * 1024;
byte[] b1 = new byte[2 * M]; // 2M
byte[] b2 = new byte[2 * M];
byte[] b4 = new byte[2 * M];
b4 = null;
byte[] b5 = new byte[2 * M];
byte[] b6 = new byte[2 * M];

}

GC Log:
[GC (Allocation Failure) [PSYoungGen: 3891K-496K(6144K)] 3891K-2640K(19968K), 0.0031407 secs] [Times: user=0.01 sys=0.01, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 4756K-496K(6144K)] 6900K-4712K(19968K), 0.0030381 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 6144K, used 4775K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000)
eden space 5632K, 75% used [0x00000007bf980000,0x00000007bfdadd80,0x00000007bff00000)
from space 512K, 96% used [0x00000007bff80000,0x00000007bfffc010,0x00000007c0000000)
to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
ParOldGen total 13824K, used 4216K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000)
object space 13824K, 30% used [0x00000007bec00000,0x00000007bf01e020,0x00000007bf980000)
Metaspace used 3165K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 345K, capacity 388K, committed 512K, reserved 1048576K

设置对象年龄阀值 MaxTenuringThreshold

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
JVM 参数:-Xmx20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:MaxTenuringThreshold=1
private static void OptimizeThree() {
int M = 1024 * 1024;
byte[] b1 = new byte[2 * M]; // 2M
byte[] b2 = new byte[2 * M];
byte[] b4 = new byte[2 * M];
b4 = null;
byte[] b5 = new byte[2 * M];
byte[] b6 = new byte[2 * M];

}

GC Log:
[GC (Allocation Failure) [PSYoungGen: 4123K-512K(6144K)] 4123K-2648K(19968K), 0.0033229 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 4770K-32K(6144K)] 6906K-4692K(19968K), 0.0037061 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 6144K, used 4309K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000)
eden space 5632K, 75% used [0x00000007bf980000,0x00000007bfdad6d0,0x00000007bff00000)
from space 512K, 6% used [0x00000007bff80000,0x00000007bff88000,0x00000007c0000000)
to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
ParOldGen total 13824K, used 4660K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000)
object space 13824K, 33% used [0x00000007bec00000,0x00000007bf08d310,0x00000007bf980000)
Metaspace used 3204K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 350K, capacity 388K, committed 512K, reserved 1048576K

对比可以发现,进行在第二次GC时,设置了对象年龄=1的对象被移到了Old区

提高系统吞吐量

减少GC执行的总时间,可以提升系统的吞吐量,可以使用关注吞吐量的ParallelGC

1
java –Xmx3072m –Xms3072m –Xmn2G –Xss128k –XX:+UseParallelGC -XX:ParallelGCThreads=2 –XX:+UseParallelOldGC

提高系统响应时间

  1. 尽可能将新生对象留在Yong区来减少Full GC的次数,Minor GC的成本要小于Full GC
  2. 使用关注响应时间的CMS GC
  3. 稍大的Survivor空间、较高的目标存活率,可以使对象直接在Young区回收,而不必因空间不够进入Old区
  4. 最大堆等于最小堆,避免堆内存的震荡从而降低系统性能,堆内存变小必然会带来更多的GC次数
1
java –Xmx3072m –Xms3072m –Xmn2G –Xss128k –XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 –XX:+UseConcMarkSweepGC –XX:MaxTenuringThreshold=31 -XX:ParallelGCThreads=2

Memory’s just a trick. - Leonard

Memento