深入JVM内核(二)常用的JVM配置参数

前言

目的:监控GC执行和JVM基本内存状态。

Trace跟踪参数

-verbose:gc or -XX:+PrintGC

打印GC的简要信息
在虚拟机发生内存回收时在输出设备显示信息

实例:

1
2
3
4
5
6
7
8
9
10
11
/**
* @author ZenoYang
* with VM args: -verbose:gc -XX:+PrintGC
*/
public class TraceTest {
public static void main(String[] args) {
Object obj = new byte[1024*1024]; // 申请1M的空间
obj = null; // 把引用obj置为null好让GC回收刚刚申请的内存空间
System.gc(); // 调用GC
}
}

输出为:

1
2
[GC (System.gc())  4355K->880K(125952K), 0.0018415 secs]
[Full GC (System.gc()) 880K->770K(125952K), 0.0083402 secs]

日志分析:
GC也叫Minor GC(年轻代GC),专门回收年轻代区域的内存。Full GC也叫Major GC(老年代GC),专门回收老年代区域的内存。4355K->880K表示GC回收了(4355-880)k的内存,两个125952K表示整个年轻代和年老代区域各占有125952K的内存,0.0018415 secs代表回收所花费的时间。

-XX:+PrintGCDetails

用于打印GC详细信息

还是刚才的例子(-XX:+PrintGCDetails),输出为:

1
2
3
4
5
6
7
8
9
10
11
[GC (System.gc()) [PSYoungGen: 4355K->856K(38400K)] 4355K->864K(125952K), 0.0468308 secs] [Times: user=0.02 sys=0.00, real=0.05 secs] 
[Full GC (System.gc()) [PSYoungGen: 856K->0K(38400K)] [ParOldGen: 8K->781K(87552K)] 864K->781K(125952K), [Metaspace: 3336K->3336K(1056768K)], 0.0223777 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
Heap
PSYoungGen total 38400K, used 333K [0x00000000d5e00000, 0x00000000d8880000, 0x0000000100000000)
eden space 33280K, 1% used [0x00000000d5e00000,0x00000000d5e534a8,0x00000000d7e80000)
from space 5120K, 0% used [0x00000000d7e80000,0x00000000d7e80000,0x00000000d8380000)
to space 5120K, 0% used [0x00000000d8380000,0x00000000d8380000,0x00000000d8880000)
ParOldGen total 87552K, used 781K [0x0000000081a00000, 0x0000000086f80000, 0x00000000d5e00000)
object space 87552K, 0% used [0x0000000081a00000,0x0000000081ac3500,0x0000000086f80000)
Metaspace used 3342K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 361K, capacity 388K, committed 512K, reserved 1048576K

日志分析:
eden表示新生代,form和to表示两个幸存代(Survivor)区域,PSPermGen表示持久代区域。
[0x00000000d5e00000, 0x00000000d8880000, 0x0000000100000000)分别代表低边界(起始的内存地址)、当前边界(当前占用到哪个位置)、最高边界(该区域最大能申请到的位置)
其它内容:emmm TODO

-XX:+PrintHeapAtGC

每次一次GC后,都打印堆信息

还是刚才的例子(-XX:+PrintHeapAtGC),输出为:

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
28
29
30
31
32
33
34
35
36
37
38
{Heap before GC invocations=1 (full 0):
PSYoungGen total 38400K, used 4355K [0x00000000d5e00000, 0x00000000d8880000, 0x0000000100000000)
eden space 33280K, 13% used [0x00000000d5e00000,0x00000000d6240dc0,0x00000000d7e80000)
from space 5120K, 0% used [0x00000000d8380000,0x00000000d8380000,0x00000000d8880000)
to space 5120K, 0% used [0x00000000d7e80000,0x00000000d7e80000,0x00000000d8380000)
ParOldGen total 87552K, used 0K [0x0000000081a00000, 0x0000000086f80000, 0x00000000d5e00000)
object space 87552K, 0% used [0x0000000081a00000,0x0000000081a00000,0x0000000086f80000)
Metaspace used 3153K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 342K, capacity 388K, committed 512K, reserved 1048576K
Heap after GC invocations=1 (full 0):
PSYoungGen total 38400K, used 840K [0x00000000d5e00000, 0x00000000d8880000, 0x0000000100000000)
eden space 33280K, 0% used [0x00000000d5e00000,0x00000000d5e00000,0x00000000d7e80000)
from space 5120K, 16% used [0x00000000d7e80000,0x00000000d7f52020,0x00000000d8380000)
to space 5120K, 0% used [0x00000000d8380000,0x00000000d8380000,0x00000000d8880000)
ParOldGen total 87552K, used 8K [0x0000000081a00000, 0x0000000086f80000, 0x00000000d5e00000)
object space 87552K, 0% used [0x0000000081a00000,0x0000000081a02000,0x0000000086f80000)
Metaspace used 3153K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 342K, capacity 388K, committed 512K, reserved 1048576K
}
{Heap before GC invocations=2 (full 1):
PSYoungGen total 38400K, used 840K [0x00000000d5e00000, 0x00000000d8880000, 0x0000000100000000)
eden space 33280K, 0% used [0x00000000d5e00000,0x00000000d5e00000,0x00000000d7e80000)
from space 5120K, 16% used [0x00000000d7e80000,0x00000000d7f52020,0x00000000d8380000)
to space 5120K, 0% used [0x00000000d8380000,0x00000000d8380000,0x00000000d8880000)
ParOldGen total 87552K, used 8K [0x0000000081a00000, 0x0000000086f80000, 0x00000000d5e00000)
object space 87552K, 0% used [0x0000000081a00000,0x0000000081a02000,0x0000000086f80000)
Metaspace used 3153K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 342K, capacity 388K, committed 512K, reserved 1048576K
Heap after GC invocations=2 (full 1):
PSYoungGen total 38400K, used 0K [0x00000000d5e00000, 0x00000000d8880000, 0x0000000100000000)
eden space 33280K, 0% used [0x00000000d5e00000,0x00000000d5e00000,0x00000000d7e80000)
from space 5120K, 0% used [0x00000000d7e80000,0x00000000d7e80000,0x00000000d8380000)
to space 5120K, 0% used [0x00000000d8380000,0x00000000d8380000,0x00000000d8880000)
ParOldGen total 87552K, used 744K [0x0000000081a00000, 0x0000000086f80000, 0x00000000d5e00000)
object space 87552K, 0% used [0x0000000081a00000,0x0000000081aba178,0x0000000086f80000)
Metaspace used 3153K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 342K, capacity 388K, committed 512K, reserved 1048576K
}

日志分析:TODO

-XX:+TraceClassLoading

监控类的加载过程

还是刚才的例子(-XX:+TraceClassLoading),输出为:

1
2
3
4
5
6
7
8
[Opened D:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar]
[Loaded java.lang.Object from D:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar]
[Loaded java.io.Serializable from D:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar]
[Loaded java.lang.Comparable from D:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar]
[Loaded java.lang.CharSequence from D:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar]
[Loaded java.lang.String from D:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from D:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar]
...(还有很多)

-XX:+PrintClassHistogram

打印类的信息

程序运行过程中,在控制台下按下Ctrl+Break时打印信息(不是上面的例子打印出来的信息):

1
2
3
4
5
6
7
8
 num     #instances         #bytes  class name
----------------------------------------------
1: 890617 470266000 [B
2: 890643 21375432 java.util.HashMap$Node
3: 890608 14249728 java.lang.Long
4: 13 8389712 [Ljava.util.HashMap$Node;
5: 2062 371680 [C
6: 463 41904 java.lang.Class

分别显示:序号、实例数量、总大小、类型

-Xloggc:log/gc.log

指定GC log的位置,以文件输出
重定向GC log,帮助开发人员分析问题

还是刚才的例子(-Xloggc:log/gc.log -XX:+PrintGCDetails),log为文件夹,需要提前创建好,gc.log为产生的日志文件

堆的分配参数

-Xmx –Xms

指定最大堆和最小堆

实例,设置最大堆为20M,最小堆为5M:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* with VM args: -Xmx20m -Xms5m
*/
public class HeapTest {
public static void main(String[] args) {
System.out.print("Xmx=");
System.out.println(Runtime.getRuntime().maxMemory()/1024.0/1024+"M");

System.out.print("free mem=");
System.out.println(Runtime.getRuntime().freeMemory()/1024.0/1024+"M");

System.out.print("total mem=");
System.out.println(Runtime.getRuntime().totalMemory()/1024.0/1024+"M");
}
}

输出为:

1
2
3
Xmx=18.0M
free mem=4.041107177734375M
total mem=5.5M

若分配1M空间给数组,在main方法最前面添加下面代码:

1
2
byte[] b=new byte[1*1024*1024];
System.out.println("分配了1M空间给数组");

输出为:

1
2
3
4
分配了1M空间给数组
Xmx=18.0M
free mem=3.017578125M
total mem=5.5M

这时候会发现剩余内存少了1M

若分配4M空间给数组:

1
2
byte[] b = new byte[4*1024*1024];
System.out.println("分配了4M空间给数组");

输出为:

1
2
3
4
分配了4M空间给数组
Xmx=18.0M
free mem=4.4584503173828125M
total mem=10.0M

这时候会发现,总内存变大了。这里做了堆的扩容。

若分配4M空间,再使用GC回收:

1
2
3
byte[] b = new byte[4*1024*1024];
System.out.println("分配了4M空间给数组");
System.gc();

输出为:

1
2
3
4
分配了4M空间给数组
Xmx=18.0M
free mem=6.177040100097656M
total mem=11.0M

会发现,剩余内存有增加了。

-Xmn

设置新生代大小(新生代包括一个eden区和两个Survivor幸存区)

实例:

1
VM args: -Xmx20m -Xms20m –Xmn7m  -XX:+PrintGCDetails

-XX:NewRatio

新生代(eden+2*s)和老年代(不包含永久区)的比值
4 表示 新生代:老年代=1:4,即年轻代占堆的1/5

-XX:SurvivorRatio

设置两个Survivor区和eden的比
8表示 两个Survivor :eden=2:8,即一个Survivor占年轻代的1/10

实例:

1
VM args: -Xmx20m -Xms20m -XX:NewRatio=1 -XX:SurvivorRatio=3 -XX:+PrintGCDetails

-XX:+HeapDumpOnOutOfMemoryError-XX:+HeapDumpPath

-XX:+HeapDumpOnOutOfMemoryError: OOM时导出堆到文件
-XX:+HeapDumpPath: 导出OOM的路径

实例:

1
2
3
4
5
6
7
8
9
10
11
/**
* with VM args: -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=e:/a.dump
*/
public class HeapTest {
public static void main(String[] args) {
Vector v = new Vector();
for(int i = 0; i < 25; i++) {
v.add(new byte[1024*1024]);
}
}
}

输出:

1
2
3
4
5
java.lang.OutOfMemoryError: Java heap space
Dumping heap to e:/a.dump ...
Heap dump file created [16423219 bytes in 0.101 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at HeapTest.main(HeapTest.java:10)

此时在E盘下产生了dump文件

-XX:OnOutOfMemoryError

在OOM时,执行一个脚本

实例:

1
2
3
4
VM args: -XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p

printstack.bat:
D:/tools/jdk1.7_40/bin/jstack -F %1 > D:/a.txt

当程序OOM时,在D:/a.txt中将会生成线程的dump
通过这个参数,可以在OOM时,发送邮件,或者重启程序等

堆分配的总结

  • 根据实际情况调整新生代和幸存代的大小
  • 官方推荐新生代占堆的3/8
  • 幸存代占新生代的1/10
  • 在OOM时,记得Dump出堆,确保可以排查现场问题

永久区参数分配

-XX:PermSize -XX:MaxPermSize

设置永久区的初始空间和最大空间
他们表示,一个系统可以容纳多少个类型

使用CGLIB等库的时候,可能会产生大量的类,这些类,有可能撑爆永久区导致OOM

1
2
3
for(int i = 0; i < 100000; i++) {
CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
}

所以,如果堆空间没有用完也抛出了OOM,有可能是永久区导致的

栈的大小分配

-Xss

通常只有几百K
决定了函数调用的深度
每个线程都有独立的栈空间
局部变量、参数 分配在栈上

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestStackDeep {
private static int count = 0;
public static void recursion(long a, long b, long c) {
long e = 1, f = 2, g = 3, h = 4, i = 5, k = 6, q = 7, x = 8, y = 9, z = 10;
count++;
recursion(a, b, c);
}
public static void main(String args[]){
try{
recursion(0L,0L,0L);
}catch(Throwable e){
System.out.println("deep of calling = "+count);
e.printStackTrace();
}
}
}

当VM参数为-Xss128K时:

1
2
3
java.lang.StackOverflowError
deep of calling = 425
...

当VM参数为-Xss256K时:

1
2
3
deep of calling = 2051
java.lang.StackOverflowError
...


参考: