2012-02-25 43 views
5

Xin chào tất cả,Tại sao các biến của tôi không nằm ngoài phạm vi?

Tôi được dạy rằng khi một hàm trả về, các biến (trong phạm vi hàm đó) tự động thoát khỏi phạm vi để chúng ta không phải đặt chúng thành rỗng.

Tuy nhiên, điều này có vẻ không đúng.

Tôi có mã thử nghiệm tạo ra một java.lang.ref.PhantomReference trỏ đến một phiên bản java.lang.Object. Tham chiếu mạnh mẽ duy nhất cho đối tượng đó nằm trong phạm vi chức năng F.

Nói cách khác, khi hàm đó trả về, sẽ không còn tham chiếu mạnh đối tượng đó nữa và đối tượng sẽ được GC.

Tuy nhiên, dù tôi có cố gắng làm sao để bỏ đói bộ nhớ JVM, GC chỉ đơn giản từ chối thu thập đối tượng. Điều đáng ngạc nhiên là nếu tôi đặt biến thành null (obj = null;), GC sẽ thu thập đối tượng.

Giải thích đằng sau sự kỳ quặc này là gì?

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

Trả lời

9

JVM tuân thủ chuẩn không bao giờ bắt buộc để thu thập bộ nhớ. Đó là để nói, bạn không thể viết một chương trình có tính chính xác phụ thuộc vào một bit cụ thể của bộ nhớ được thu thập tại một thời điểm nhất định: bạn không thể buộc JVM để thu thập (thậm chí thông qua System.gc()!) Cũng không dựa vào nó làm như vậy.

Vì vậy, hành vi bạn đang quan sát không thể, theo nghĩa đen là sai: bạn đang cố gắng cố gắng làm cho môi trường làm điều gì đó mà không phải làm việc.

Điều đó tất cả đã nói, vấn đề của bạn là đối tượng của bạn đã không vượt quá phạm vi. Nó được tạo trong main, sau đó được chuyển - theo cách tham chiếu Java thông thường - đến F. Cho đến khi trả về F, tên T obj vẫn là một tham chiếu đến đối tượng của bạn.

Đặt goOom tĩnh và thực hiện cuộc gọi đến số main và bạn sẽ thấy đối tượng được thu thập. Nhưng, sau đó một lần nữa, bạn có thể vẫn không, và đó sẽ không phải là sai ...

+0

Điều này làm cho destructors hoàn thành botch-up như xa như lỗi thiết kế ngôn ngữ đi. – tchrist

+0

@ Borealid Tuy nhiên không phải là toàn bộ điểm của java.lang.ref.PhantomReference sao cho chúng ta có thể được thông báo bất cứ khi nào một đối tượng rời khỏi heap? Sẽ không phải là một * kiêu ngạo * (vì thiếu một từ tốt hơn) JVM làm cho toàn bộ lớp vô dụng? – Pacerier

+1

+1, và chỉ để làm rõ, một tham chiếu yếu nói chung là enqueued khi GC xác định rằng nó * có thể * được GCed, trong khi tham chiếu ma là enqueued khi GC có bước tiếp theo và thực sự GCs nó. Đó là một chút mơ hồ và không hoàn toàn chính xác; xem [ở đây] (http://docs.oracle.com/javase/6/docs/api/java/lang/ref/package-summary.html#reachability) để có định nghĩa chính xác hơn. – yshavit

Các vấn đề liên quan