2012-02-25 62 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