今天看到一篇文章,讨论【不使用的对象应手动赋值为null】这件事。

文中给了这样一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
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回收掉。

而下面这段代码就会被回收:

1
2
3
4
5
6
7
8
9
10
11
12
13
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判断这两个对象都是存活的,不进行回收。

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

1
2
3
4
5
6
7
8
9
10
11
 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时不会被回收。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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因为使用了不妥当的例子而得到了错误的结论。