变量在catch块后立即收集垃圾

时间:2012-07-13 16:42:19

标签: java garbage-collection

我可以从垃圾收集器中看到这种奇怪的行为

public class A {
    public static void main(String[] args) {

        String foo;
        try {
            foo = "bar";

            int yoo = 5; //1
        } catch (Exception e) {
        }

        int foobar = 3;//2 
    }
}

如果我去调试并在//上设置断点,则foo不为null且其值为“bar”但在断点中// 2 foo为null,这在调试时很难理解。我的问题是,是否有任何规范说这是垃圾收集器的合法行为

通过这种小变化,它不会收集垃圾:

public class A {
    public static void main(String[] args) {

        String foo;
        try {
            foo = "bar";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        int foobar = 3;
    }
}

为什么?

4 个答案:

答案 0 :(得分:9)

在这种情况下,您在设置之后不使用foo变量,因此JVM完全忽略变量甚至是合法的,因为它从未使用过,并且不会改变程序的结果。 / p>

然而,在调试模式下不太可能发生这种情况。

在你的情况下,只要foo在范围内或你持有对它的引用(包括try / catch块之后的部分),foo就不应该得到GC。

修改

实际上我得到的行为与你在Netbeans 7.1.1中用Java 7.0_03描述的行为相同......

一个问题可能是因为你没有将默认值设置为foo,所以你不能在try / catch块之后使用它(它不会编译)。

<强> Bytcode

  • 使用您使用的代码
 
public static void main(java.lang.String[]);
Code:
   0: ldc           #2                  // String bar
   2: astore_1      
   3: iconst_5      
   4: istore_2      
   5: goto          9
   8: astore_2      
   9: iconst_3      
  10: istore_2      
  11: return        
  • 使用String foo = null;作为第一个语句,在这种情况下,调试器会看到try / catch块之后的值:
 
public static void main(java.lang.String[]);
Code:
   0: aconst_null   
   1: astore_1      
   2: ldc           #2                  // String bar
   4: astore_1      
   5: iconst_5      
   6: istore_2      
   7: goto          11
  10: astore_2      
  11: iconst_3      
  12: istore_2      
  13: return        

我不是代码专家,但他们看起来和我很相似......

结论

我个人的结论是,为了让调试器显示foo的值,它必须运行某种foo.toString(),这不是catch块之后的有效语句{{1可能尚未初始化。在该部分中添加foo是不合法的(不编译)。调试器有点丢失,显示值是什么,并显示System.out.println(foo)

为了说服自己这与GC无关,您可以尝试以下示例:

null

public static void main(String[] args){ String foo; char[] c = null; try { foo = "bar"; c = foo.toCharArray(); int yoo = 5; //1 } catch (Exception e) { } int foobar = 3;//2 } 行,您可以看到foobar持有c但foo显示为bar。所以String仍然存在,但是调试器无法显示它。

更有趣的例子:

null

public static void main(String[] args){ String foo; List<String> list = new ArrayList<String>(); try { foo = "bar"; list.add(foo); int yoo = 5; //1 } catch (Exception e) { } int foobar = 3;//2 } 行,foobar显示为foo,但null包含list ...很好。

答案 1 :(得分:2)

foo从不具有默认值,当您转到第2行时,您将超出设置范围。

答案 2 :(得分:2)

生成的字节代码如下:

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String bar
       2: astore_1
       3: iconst_5
       4: istore_2
       5: goto          9
       8: astore_2
       9: iconst_3
      10: istore_2
      11: return
    Exception table:
       from    to  target type
           0     5     8   Class java/lang/Exception

我的第一个猜测是,我们为foo和foobar重用了局部变量位置,在这种情况下,调试时不再显示该值。但是可以看出本地1没有被覆盖(yoo和foobar虽然共享相同的空间)。

由于这种情况没有发生,我们可以非常肯定JIT在这里没有做任何事情,这是非常奇怪的行为。

答案 3 :(得分:-2)

这不是合法行为,因为主要方法的上下文仍然存在。除非你在try块中声明了String foo,否则理想情况下不应该获取GCed(在这种情况下,它将无法在2处访问)。

我尝试了这段代码,它对我来说很好。