2012-02-25 65 views
5

大家下午好,为什么我的变量不能超出范围?

我被告知当一个函数返回时,变量(在该函数的范围内)自动超出范围,所以我们不必将它们设置为null。

但是,这似乎并不正确。

我有一个测试代码,创建一个指向java.lang.Object的实例的java.lang.ref.PhantomReference。对该对象的唯一强引用在函数F的范围内。

换句话说,当该函数返回时,应该不再有任何对该对象的强引用,并且该对象现在应该可由GC。

但是,无论我多么努力地试图让内存的JVM挨饿,GC只是拒绝收集对象。令人惊讶的是,如果我将该变量设置为空(obj = null;),则GC现在收集该对象。

这种古怪背后的解释是什么?符合标准的

public class Test { 
    public static void main(String args[]) { 
     // currently testing on a 64-bit HotSpot Server VM, but the other JVMs should probably have the same behavior for this use case 
     Test test = new Test(); 
     test.F(new Object()); 
    } 

    public <T> void F(T obj) { 
     java.lang.ref.ReferenceQueue<T> ref_queue = new java.lang.ref.ReferenceQueue<T>(); 
     java.lang.ref.PhantomReference<T> ref = new java.lang.ref.PhantomReference<T>(obj, ref_queue); // if this line isn't an assignment, the GC wouldn't collect the object no matter how hard I force it to 
     obj = null; // if this line is removed, the GC wouldn't collect the object no matter how hard I force it to 
     StartPollingRef(ref_queue); 
     GoOom(); 
    } 

    private <T> void StartPollingRef(final java.lang.ref.ReferenceQueue<T> ref_queue) { 
     new java.lang.Thread(new java.lang.Runnable() { 
      @Override 
      public void run() { 
       System.out.println("Removing.."); 
       boolean removed = false; 
       while (!removed) { 
        try { 
         ref_queue.remove(); 
         removed = true; 
         System.out.println("Removed."); 
        } catch (InterruptedException e) { // ignore 
        } 
       } 
      } 
     }).start(); 
    } 

    private void GoOom() { 
     try { 
      int len = (int) java.lang.Math.min(java.lang.Integer.MAX_VALUE, Runtime.getRuntime().maxMemory()); 
      Object[] arr = new Object[len]; 
     } catch (Throwable e) { 
      // System.out.println(e); 
     } 
    } 
} 

回答

9

一个JVM从不有义务来收集内存。也就是说,你不能编写一个程序,其正确性取决于在特定时间收集的特定内存位:你既不能强制JVM收集(即使通过System.gc()!)也不能依赖它。

因此,您所观察到的行为不可能在定义上是错误的:您有意试图让环境做一些事情,而不是做任何事情。

大家都说,你的问题是你的对象还没有超出范围。它在main中创建,然后以普通的Java引用方式传递到F。直到F返回,T obj名称仍然是对您的对象的引用。

goOom设为静态并在main中拨打电话,您应该看到收集的对象。但是,再次,你可能仍然不会,并且那不会是错误 ...

+0

这使得析构函数在语言设计错误发生时是一个完整的错误。 – tchrist 2012-02-25 01:33:27

+0

@Borealid然而,不是java.lang.ref.PhantomReference的全部内容,只要对象离开堆,我们都可以得到通知。不会*傲慢*(缺乏更好的词)JVM渲染整个类无用吗? – Pacerier 2012-02-25 01:37:37

+1

+1,并且为了澄清,当GC确定它可以被GCC化时,通常将弱参考排入队列,而当GC进行下一步并且实际GC时,虚拟参考被排队。这有点模糊,并不完全准确;请参阅[这里](http://docs.oracle.com/javase/6/docs/api/java/lang/ref/package-summary.html#reachability)以获得更准确的定义。 – yshavit 2012-02-25 01:38:15