又见【不使用的对象应手动赋值为null】

今天看到 一篇文章
,讨论 【不使用的对象应手动赋值为null】
这件事。
文中给了这样一个例子:

public static void main(String[] args) {
    if (true) {
        byte[] placeHolder = new byte[64 * 1024 * 1024];
        System.out.println(placeHolder.length / 1024);
    }
    System.gc();
}

执行结果:
65536
[GC 68239K->65952K(125952K), 0.0014820 secs]
[Full GC 65952K->65881K(125952K), 0.0093860 secs]

上面这段代码执行gc后内存占用没有降下来,没有把placeHolder回收掉。
而下面这段代码就会被回收:

public static void main(String[] args) {
    if (true) {
        byte[] placeHolder = new byte[64 * 1024 * 1024];
        System.out.println(placeHolder.length / 1024);
        placeHolder = null;
    }
    System.gc();
}

执行结果:
65536
[GC 68239K->65952K(125952K), 0.0014910 secs]
[Full GC 65952K->345K(125952K), 0.0099610 secs]

文章认为原因是 触发GC时,main()方法的运行时栈中,还存在有对args和placeHolder的引用,GC判断这两个对象都是存活的,不进行回收。

事实真的是这样吗?
我们不妨看看第一段代码的字节码大家就明白了。

 0: ldc           #2                  // int 67108864
 2: newarray       byte
 4: astore_1
 5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
 8: aload_1
 9: arraylength
10: sipush        1024
13: idiv
14: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
17: invokestatic  #5                  // Method java/lang/System.gc:()V
20: return

从字节码可以看到, if(true)
在编译时被优化掉了,代码相当于变成了下面这样,placeHolder的作用域变大了,所有在GC时不会被回收。

public static void main(String[] args) {
    byte[] placeHolder = new byte[64 * 1024 * 1024];
    System.out.println(placeHolder.length / 1024);
    System.gc();
}

现在我们来换个写法,不让if条件被优化掉:

public static void main(String[] args) {
    if (args.length == 0) {
        byte[] placeHolder = new byte[64 * 1024 * 1024];
        System.out.println(placeHolder.length / 1024);
    }
    System.gc();
}

执行结果:【省略了部分无关的GC输出】
65536
[GC (System.gc()) 70793K->66152K(251392K), 0.0008739 secs]
[Full GC (System.gc()) 66152K->458K(251392K),0.0041778 secs]

可以看到,GC后placeHolder被回收了。
关于赋不赋值为null这件事,看个人编码习惯,我一般不喜欢做这些多余的操作,实际对GC也不会产生什么影响。那篇文章后面关于局部变量表重用的讨论是没问题的,但关于赋值为null因为使用了不妥当的例子而得到了错误的结论。