前段时间发了一篇 又见【不使用的对象应手动赋值为null】 ,之后又想了想,发现在变量会不会被回收这个问题上还是有一些地方说不清楚,于是再次研究了一下。
还是用上篇文章的例子,我们来看看字节码。
// placeHolder不会被回收 public static void main(String[] args) { if (true) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } System.gc(); } // 字节码 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 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 LocalVariableTable: Start Length Slot Name Signature 5 12 1 placeHolder [B 0 21 0 args [Ljava/lang/String; // placeHolder会被回收 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(); } // 字节码 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: aload_0 1: arraylength 2: ifne 22 5: ldc #2 // int 67108864 7: newarray byte 9: astore_1 10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 13: aload_1 14: arraylength 15: sipush 1024 18: idiv 19: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 22: invokestatic #5 // Method java/lang/System.gc:()V 25: return LocalVariableTable: Start Length Slot Name Signature 10 12 1 placeHolder [B 0 26 0 args [Ljava/lang/String;
LocalVariableTable
里虽然标明了 placeHolder
的作用范围,第一个例子是从偏移5到17,第二个例子是从10到22,但是终止的偏移都是 invokestatic #5 // Method java/lang/System.gc:()V
,如果是因为作用域的原因,那这两种情况下作用域应该是一样的才对。
另外如果真的是我之前想的作用域的原因,那下面这样的代码为什么也不会被回收掉呢?
public static void main(String[] args) { if (args.length == 0) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } else { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } System.gc(); }
这让我产生了怀疑?
我在测试上面两种代码的时候,都是通过IDE运行的,在这种情况下是通过解释执行的方式运行。
可以在运行时加上 -Xcomp
或 -Xjit:count=0
的参数,让JVM强制只用编译执行的方式运行,加上参数后,会发现上面两种代码中的 placeHolder
都会被顺利回收。
即使是下面这样的代码,加上上述参数后 placeHolder
都会被回收。
public static void main(String[] args) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); System.gc(); }
在不添加JVM参数的情况下,除了上面提到的那些例子,以下几种代码 placeHolder
都不会被回收。
public static void main(String[] args) { { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } System.gc(); } public static void main(String[] args) { do { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } while(false); System.gc(); } public static void main(String[] args) { do { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } while(args.length != 0); System.gc(); } public static void main(String[] args) { if (true) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } else { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } System.gc(); }
而下面的例子则可以被回收:
public static void main(String[] args) { int i = 0; if (i == 0) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } System.gc(); } public static void main(String[] args) { for (int i = 0; i == 0; i++) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } System.gc(); } public static void main(String[] args) { int i = 0; while (i++ == 0) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } System.gc(); } // 下面这种代码稍微有点不同 public static void main(String[] args) { if (true) { byte[] placeHolder = new byte[64 * 1024 * 1024]; System.out.println(placeHolder.length / 1024); } int i = 0; System.gc(); }
这两种类型的代码有什么区别呢?
在第一类的代码中, placeHolder
这个变量是 一定
会被初始化并赋值的。
而在第二类代码中,虽然在目前的参数或代码下 placeHolder
会被初始化赋值,但 存在
着 placeHolder
没有初始化的情况或分支。不过最后一种代码不太一样,最后一种是因为局部变量表slot复用导致的。
根据上面我看到的现象,在此我作出大胆猜测:
System.gc()
以上猜测还需要之后深入研究下JVM的代码来验证,不过有一点可以肯定的是,我们没必要在所有变量使用完后都设置为null,毕竟绝大部分情况下运行的都是JIT编译之后的代码,即便手动设置为null,我猜也是会在及时编译时被优化掉的。