声明:以下环境均是JDK1.8
参考:实战Java虚拟机
1 GC打印参数
-XX:+PrintGC
遇到GC就会打印日志
-XX:+PrintGCDetails
打印更详细信息,JVM退出前会打印JVM详情
-XX:+PrintHeapAtGC
GC前后打印堆信息
-XX:+PrintGCTimeStamps
会在当前GC时,打印当前启动后的时间(比如程序启动了0.08s、0.88s之类的这个时间点)
-XX:+PrintGCApplicationConcurrentTime 打印程序执行时间
-XX:+PrintGCApplicationStoppedTime 打印程序结束时间
2 类加载/卸载的跟踪
-verbose:class
跟踪类的加载和卸载过程
3 堆参数配置
可用-Xms、-Xmx指定堆大小,以下是一个JVM参数设置的图:
上图的参数如下:
1 新生代大小配置:-Xmn,新生代会减少老年代大小,一般设置为堆的1/3或1/4
2 新生代eden与from/to比例关系及效果:
------------代码----------------------------
byte[] b = null;
for (int i = 0; i < 10; i++) {
b = new byte[1 * 1024 * 1024];
}
--------------------------------------------
2.1 使用java -Xmx20m -Xms20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails NewSizeDemo 结果如下:
- 分析:首先设置了堆的大小为20m,然后设置eden与from的比例=2,并打印了GC日志。由于这里新生代分配到3584K(Eden) + 1536K(From) = 5120K,并且循环遍历10次,每次分配1m内存,eden区到第四次就不够,要进行回收,通过GC确保新生代有足够空间。
2.2 把程序的new byte[1 * 1024 * 1024] 改为 new byte[5 * 1024 * 1024],这样每次会分配5M内存,3584K(Eden空间大小)无法容纳单次分配的5M大小,故所有空间分配只能分到老年代中(图中老年代可见用了大概10M,刚好是2 * (5*1024*1024)),两次内存分配,原因是老年代只有13M,最多只能容纳两次内存分配,第三次就会回收。
2.3 把new byte[5 * 1024 * 1024] 改为 new byte[1 * 1024 * 1024],每次分配1m内存。然后使用参数如下:
# java -Xmx20m -Xms20m -Xmn15m -XX:SurvivorRatio=8 -XX:+PrintGCDetails NewSizeDemo
因为上面参数修改栈的大小=15m,eden/from=8,所以eden区空间>10m,所以就算循环分配10m的空间,下面也不会进行一次回收。
2.4 还可以通过-XX:NewRatio=n 来设置新生代/老年代 的比例。
2.5 当eden区大于当次分配内存(1M),并且from小于(1M)的话,新生代需要老年代做空间担保,1MB数组会进入老年代。
看完上面的参数设置过程,我们再来回顾下下面的图(好好回忆下):
3 堆溢出处理
为了记录堆溢出时的信息,可添加如下参数:
-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/xxx.dump
以上在发生堆溢出时,就会输出文件到xxx.dump,然后可用MAT分析(后面会讲)