大家先看段代码:
1 //启动参数设置:-Xms20m -Xmx20m -XX:SurvivorRatio=8 -Xmn10m
2 public static void main(String[] args) {
3 @SuppressWarnings("unused")
4 byte[] b1,b2,b3,b4;
5 long start = System.currentTimeMillis();
6 int i = 1;
7 while(i++ < 1000){
8 b1 = new byte[1 * _1M];
9 b2 = new byte[4 * _1M];
10 b2 = null;//这行注释掉的话,运行时间会不会有很大的不同呢?
11 b2 = new byte[4 * _1M];
12 }
13 System.out.println(System.currentTimeMillis() - start);
14 }
先把我本地测试的结果贴出来:
有 b2=null | 没有 b2=null |
5922 ms | 14749 ms |
6187 ms | 15077 ms |
6066 ms | 16021 ms |
--------------------------------------------------------------------------------------
从结果可以看出来,在显示调用引用为null时,运行相同的代码1000次,时间竟然相差两倍有余。为何?
我们先把上面代码改成只运行一次,并设置参数打印GC详细日志:
1 //启动参数设置:-Xms20m -Xmx20m -XX:SurvivorRatio=8 -Xmn10m -XX:+PrintGCDetails
2 public static void main(String[] args) {
3 @SuppressWarnings("unused")
4 byte[] b1, b2, b3, b4;
5 b1 = new byte[1 * _1M];
6 b2 = new byte[4 * _1M];
7 b2 = null;//先不注释运行,再把这行注释掉运行一次,比较两次的GC日志
8 b2 = new byte[4 * _1M];
9 }
GC日志如下:
结论:当显示设置实例为null时,GC后的内存容量和GC消耗的时间都是不同的。此时显示设置为null的话,GC后已占用的堆内存更小,消耗时间更短。
至于为什么为这样,其实上面的GC详细日志内存的使用比例已经能说明问题,对于不是很清楚jvm内存模型的同学来说,还是看得不太清楚,下面我用简单的图讲解原因:
先看下内存管理图(这张图截至阿里温少的PPT)
至于详细的各内存块的含义,大家可以在网上找资料。针对这文章中的示例代码运行参数(-Xms20m -Xmx20m -XX:SurvivorRatio=8 -Xmn10m)设置,将会这样分配内存:
Eden | 8m |
s0 | 1m |
s1 | 1m |
tenured | 10m |
我们来分别详细分析下上面两种情况下内存的详细分配:
第一种情况(不显示设置null时)
b1 = new byte[1 * _1M];
当执行这条语句时在Eden区分配1m内存。
b2 = new byte[4 * _1M];
当执行这语句时再往Eden区分配4m内存。
b2 = new byte[4 * _1M];
当执行这条语句时,虚拟机想再从Eden区申请4m内存,发现不够了(8m - 1m - 4m = 3m),于是进行一次GC操作。由于Eden区的两个内存块(b1为1m,b2为4m)现在都还不是可回收对象,所以会复制到survivor区,但是survivor区才1m空间放不下,于是复制到Tenured区。然后将Eden区块全部清空回收,这样Eden区又有8m空间了,可以将刚才的b2 = new byte[4 * _1M]放到eden区。最终内存情况就是下面的:
第二种情况(显示设置null时)
b1 = new byte[1 * _1M];
当执行这条语句时在Eden区分配1m内存。
b2 = new byte[4 * _1M];
当执行这语句时再往Eden区分配4m内存。
b2 = null;
将第二步分配的4m字节数组标志为无效对象(空引用,待回收)
b2 = new byte[4 * _1M];
重新申请4m空间,发现不够了(8m - 1m - 4m = 3m),于是进行一次GC操作。此时发现在Eden区中,b1指向的1m的字节数组为有效对象,而别一个4m的字节数组为无效对象(b2=null)。如是将1m字节数组内存复制到Tenured区中(只复制b1指向的实例)。然后将Eden区块全部清空回收,这样Eden区又有8m空间了,可以将刚才的b2 = new byte[4 * _1M]放到eden区。最终内存情况就是下面的:
总结:
我写这篇文章并不是想说明代码要显示设置无用对象为null还是不要设置。因为我这里举的例子中vm参数设置有点偏激,只是为了更好的说明问题,在生产中决不会设置内存20M,然后一个对象还占几M的空间这样的比例。
在一般的情况下,内存1G,对象实例也很小时,这个GC的时间回收点问题不会像本文的问题那样明显。所以权当学习而也。