7

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

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 
    }
}

如果我去调试并在 //1 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

4 回答 4

9

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

但是,这在调试模式下不太可能发生。

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

编辑

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

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

字节码

  • 使用您使用的代码
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()某种类型的 a,这在 catch 块之后不是有效的语句,因为foo可能尚未初始化。在该部分添加 aSystem.out.println(foo)是不合法的(不编译)。调试器对于值是什么和显示有点迷失null

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

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成立bar,但 foo 显示为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包含"bar"...很好。

于 2012-07-13T16:45:47.353 回答
2

foo从来没有默认值,当您转到第 2 行时,您正在超出设置它的范围。

于 2012-07-13T16:48:02.513 回答
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 重用局部变量位置,在这种情况下,调试时该值将不再可用。但是可以看出 local 1 没有被覆盖(虽然 yoo 和 foobar 共享相同的空间)。

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

于 2012-07-13T16:56:33.057 回答
-2

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

我尝试了这段代码,它对我来说工作正常。

于 2012-07-13T16:51:43.100 回答