打印 GC 日志
这里以 IDEA 为例:
菜单栏:
Run -> Edit Configurations -> VM options 添加 -XX:+PrintGCDetail
写个简单的测试程序
package VM;
/**
* Created by andy.wwh on 2016/7/16.
*/
public class TestPrintGcDetails {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
byte[] b = new byte[1*1024*1024];
}
}
}
上图中除了各个区的内存分配情况,还发生了一次 GC。
GC日志:[GC (Allocation Failure) [PSYoungGen: 1024K->488K(1536K)] 1024K->648K(19968K), 0.0007228 secs] [Times: user=0.05 sys=0.00, real=0.00 secs]
从这条日志我们可以看出新生代 YoungGen 分配失败,1024K->488K(1536) 表示 GC 前已使用->GC后已使用(该区域总容量),[]之外表示Java堆GC前已使用->GC后已使用(Java堆总容量),最后为 GC 耗时。
从上图我们可以看出 GC 的主要收集区域,包括 PSYoungGen(年轻代)、ParOldGen(老年代)、Metaspace(元数据区)。
上图中却没有 Metaspace,而多了 Permanent Generation,原因 JDK 1.8 中用 Metaspace 取代了 Permanent。
-XX:PrintHeapAtGC 打印 GC 前后的堆分配信息
从上图我们可以看到添加了 -XX:PrintHeapAtGC 参数后可以清楚的看到堆分配信息,而且还可以看到 GC 数据的移动 PSYoungGen.eden space -> PSYoungGen.from space -> ParOldGen.object space。
-XX:+TraceClassLoading
堆分配参数
测试代码:
public class TestMaxMinHeap {
public static void main(String[] args) {
System.out.println("maxMemory:" + ( Runtime.getRuntime().maxMemory()/1024/1024) + "M");
System.out.println("freeMemory:" + (Runtime.getRuntime().freeMemory()/1024/1024) + "M");
System.out.println("totalMemory:" + (Runtime.getRuntime().totalMemory()/1024/1024) + "M");
}
}
未修改参数
-Xmx 和 -Xms
最大堆:-Xmx20m 设置 maxMemory 为 20 M
分配空间不能超过最大堆内存大小,否则会抛出 OutofMemory 异常
初始化堆:-Xms20m 设置 total Memory 为 20 M
totalMemory 就是初始化堆大小,它的意思是一开始限定堆大小为多少,如果不够则可以扩充,但必须小于最大堆大小。
关系如下:
我们可以做个简单的小测试,最小堆设置为 5 M,最大堆设置为 20 M,分配内存来看看它们数值的变化。
/**
* VM options:-Xmx20m -Xms5m
*/
public class TestMaxMinHeap {
private static final int M = 1024 * 1024;
public static void main(String[] args) {
System.out.println("maxMemory:" + ( Runtime.getRuntime().maxMemory()/M) + "M");
System.out.println("freeMemory:" + (Runtime.getRuntime().freeMemory()/M) + "M");
System.out.println("totalMemory:" + (Runtime.getRuntime().totalMemory()/M) + "M");
}
}
上图可以看出最大堆是 18 M,初始堆是 5 M,空闲堆是 4 M。
我们分配 2 M 来看看。
/**
* VM options:-Xmx20m -Xms5m
*/
public class TestMaxMinHeap {
private static final int M = 1024 * 1024;
public static void main(String[] args) {
byte b[] = new byte[2 * M];
System.out.println("maxMemory:" + ( Runtime.getRuntime().maxMemory()/M) + "M");
System.out.println("freeMemory:" + (Runtime.getRuntime().freeMemory()/M) + "M");
System.out.println("totalMemory:" + (Runtime.getRuntime().totalMemory()/M) + "M");
}
}
最大和初始堆大小都不变,空闲数变为 2 M,耗费 2 M。
分配 6 M 来看看。
/**
* VM options:-Xmx20m -Xms5m
*/
public class TestMaxMinHeap {
private static final int M = 1024 * 1024;
public static void main(String[] args) {
byte b[] = new byte[6 * M];
System.out.println("maxMemory:" + ( Runtime.getRuntime().maxMemory()/M) + "M");
System.out.println("freeMemory:" + (Runtime.getRuntime().freeMemory()/M) + "M");
System.out.println("totalMemory:" + (Runtime.getRuntime().totalMemory()/M) + "M");
}
}
可以看到,初始堆 大小从 5 M 变为了 12 M。说明初始堆是可以动态调整的。
分配大于最大堆试试。
/**
* VM options:-Xmx20m -Xms5m
*/
public class TestMaxMinHeap {
private static final int M = 1024 * 1024;
public static void main(String[] args) {
byte b[] = new byte[21 * M];
System.out.println("maxMemory:" + ( Runtime.getRuntime().maxMemory()/M) + "M");
System.out.println("freeMemory:" + (Runtime.getRuntime().freeMemory()/M) + "M");
System.out.println("totalMemory:" + (Runtime.getRuntime().totalMemory()/M) + "M");
}
}
上图可以看出,超过最大堆时就会出现溢出问题了。
我们可以根据自己应用的时机情况来动态调整该参数,提高性能,一般在高并发请款下,建议 -Xms 和 -Xmx 值相同,避免内存 收缩/增大 出现的性能问题。
-Xmn、-newRatio、-SurvivorRatio:
- -Xmn:设置 新生代 大小
- -NewRatio:设置 新生代 和 老年代 的比率
- -SurvivorRatio:设置新生代中 Eden space 和 Survivor space 的大小
我们来看测试它们的作用:
public class TestNewSpace {
private static final int M = 1024*1024;
public static void main(String[] args) {
byte[] b = null;
for (int i = 0; i < 10; ++i) {
b = new byte[1*M];
}
}
}
参数:-Xmn1m -Xms20m -Xmx20m -XX:+PrintGCDetails
新生代大小设置为 1 M
结论:出发了两次 GC。当新生代太小时,会频繁触发 GC
参数:-Xmn15m -Xms20m -Xms20m -XX:PrintGCDetails
新生代大小设置为 15 M
结果:没有触发 GC,当 新生代 比较大时,对象全部分配到 新生代。
参数:-Xmn7m -Xms20m -Xms20m -XX:PrintGCDetails
新生代设置不大不小
结果:触发两次 GC
参数:-Xmn7m -Xms20m -Xms20m -XX:PrintGCDetails -XX:SurvivorRatio=2
将新生代中的幸存代设大
结果:触发了三次 GC
官方推荐
- 根据实际情况调整新生代和幸存代的大小
- 官方推荐新生代占堆的 3/8
- 幸存代占新生代的 1/10
错误时参数
- -XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpPath: 根据 OOM 错误导出 dump 文件,指出路径
- -XX:OnOutOfMemoryError:XXX.py 发生 OOM 时执行某个脚本
栈分配参数
-Xss
指定 线程栈 大小,如:-Xss128k,一般来说,如果程序有大规模的递归行为,可以设置到 512K-1M。
public class TestXssStack {
public static int count = 0;
public static void func() {
count++;
func();
}
public static void main(String[] args) {
try {
func();
}catch (Throwable e){
System.out.println("count:" + count);
}
}
}
没调整参数时,调用了 30343 次。
设置 -Xss2m
调整参数后调用了 92111 次。