2010-03-20 25 views
5

Hãy xem xét một đối tượng khai báo trong một phương pháp:Tại sao một đối tượng được khai báo trong phương thức phải chịu thu gom rác trước khi phương thức trả về?

public void foo() { 
    final Object obj = new Object(); 

    // A long run job that consumes tons of memory and 
    // triggers garbage collection 
} 

sẽ obj bị thu gom rác thải trước foo() trả về?

CẬP NHẬT: Trước đây tôi nghĩ obj là không thuộc đối tượng thu gom rác thải cho đến khi foo() trả về.

Tuy nhiên, hôm nay tôi thấy mình sai.

Tôi đã dành vài giờ để sửa lỗi và cuối cùng phát hiện ra sự cố là do rác thải obj thu thập được!

Có ai giải thích tại sao điều này xảy ra không? Và nếu tôi muốn obj được ghim làm thế nào để đạt được nó?

Đây là mã có sự cố.

public class Program 
{ 
    public static void main(String[] args) throws Exception { 
     String connectionString = "jdbc:mysql://<whatever>"; 

     // I find wrap is gc-ed somewhere 
     SqlConnection wrap = new SqlConnection(connectionString); 

     Connection con = wrap.currentConnection(); 
     Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, 
      ResultSet.CONCUR_READ_ONLY); 
     stmt.setFetchSize(Integer.MIN_VALUE); 

     ResultSet rs = stmt.executeQuery("select instance_id, doc_id from 
       crawler_archive.documents"); 

     while (rs.next()) { 
      int instanceID = rs.getInt(1); 
      int docID = rs.getInt(2); 

      if (docID % 1000 == 0) { 
       System.out.println(docID); 
      } 
     } 

     rs.close(); 
     //wrap.close(); 
    } 
} 

Sau khi chạy chương trình Java, nó sẽ in được thông báo sau trước khi nó bị treo:

161000 
161000 
******************************** 
Finalizer CALLED!! 
******************************** 
******************************** 
Close CALLED!! 
******************************** 
162000 
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: 

Và đây là đoạn code của lớp SqlConnection:

class SqlConnection 
{ 
    private final String connectionString; 
    private Connection connection; 

    public SqlConnection(String connectionString) { 
     this.connectionString = connectionString; 
    } 

    public synchronized Connection currentConnection() throws SQLException { 
     if (this.connection == null || this.connection.isClosed()) { 
      this.closeConnection(); 
      this.connection = DriverManager.getConnection(connectionString); 
     } 
     return this.connection; 
    } 

    protected void finalize() throws Throwable { 
     try { 
      System.out.println("********************************"); 
      System.out.println("Finalizer CALLED!!"); 
      System.out.println("********************************"); 
      this.close(); 
     } finally { 
      super.finalize(); 
     } 
    } 

    public void close() { 
     System.out.println("********************************"); 
     System.out.println("Close CALLED!!"); 
     System.out.println("********************************"); 
     this.closeConnection(); 
    } 

    protected void closeConnection() { 
     if (this.connection != null) { 
      try { 
       connection.close(); 
      } catch (Throwable e) { 
      } finally { 
       this.connection = null; 
      } 
     } 
    } 
} 
+1

Bạn không in ra thông báo trong 'finalize()', bạn đang in nó ra trong 'close()', và bạn đang gọi 'close()'. Di chuyển thư vào trình hoàn thiện và thử lại. – skaffman

+1

Bạn nên đăng câu hỏi riêng để cập nhật. –

+0

@skaffman: Tôi nên xóa phần đóng() khỏi chính. Vấn đề vẫn còn tồn tại. –

Trả lời

3

Khi mã của bạn là , hãy viết đối tượng được trỏ tới bằng "bọc" không đủ điều kiện để thu thập rác cho đến khi "bọc" bật ra khỏi ngăn xếp ở cuối phương thức.

Thực tế là nó đang được thu thập gợi ý cho tôi mã của bạn như biên soạn không phù hợp với nguồn gốc và trình biên dịch đã thực hiện một số tối ưu hóa như thay đổi này:

SqlConnection wrap = new SqlConnection(connectionString); 
Connection con = wrap.currentConnection(); 

này:

Connection con = new SqlConnection(connectionString).currentConnection(); 

(Hoặc thậm chí là nội tuyến toàn bộ) vì "bọc" không được sử dụng ngoài điểm này. Đối tượng ẩn danh được tạo sẽ đủ điều kiện cho GC ngay lập tức.

Cách duy nhất để chắc chắn là dịch ngược mã và xem những gì đã được thực hiện với mã đó.

+0

Tôi thấy đây là nguyên nhân gốc rễ của vấn đề. –

0

Ở đây, obj là một biến cục bộ trong phương thức và nó được bật ra khỏi ngăn xếp ngay khi phương thức trả về hoặc thoát. Điều này không có cách nào để đạt được các đối tượng Object trên đống và do đó nó sẽ được thu gom rác thải. Và đối tượng Object trên heap sẽ được GC chỉ sau khi tham chiếu của nó obj được bật ra khỏi ngăn xếp, nghĩa là, chỉ sau khi phương thức kết thúc hoặc trả về.


EDIT: Để trả lời cập nhật của bạn,

UPDATE: Let me make the question more clear. 
Will obj be subject to garbage collection before foo() returns? 

obj chỉ là một tài liệu tham khảo đến đối tượng thực tế trên đống. Ở đây obj được khai báo bên trong phương thức foo(). Vì vậy, câu hỏi của bạn Will obj be subject to garbage collection before foo() returns? không áp dụng như obj đi vào bên trong khung ngăn xếp khi phương thức foo() đang chạy và biến mất khi phương thức kết thúc.

+0

obj là một kiểu tham chiếu, do đó chỉ tham chiếu (con trỏ) được lưu trữ trên ngăn xếp, dữ liệu thực tế nằm trong vùng heap. –

+0

@Simon P Stevens, đó là chính xác những gì tôi đã đề cập. Biến tham chiếu obj là LOCAL VARIABLE. – Zaki

+0

Xin lỗi. Không đọc các thẻ. Suy nghĩ của. (Tôi đã chỉnh sửa để tôi có thể thu hồi giấy phép của tôi). Lấy làm tiếc. –

2

Thực sự có hai điều khác nhau đang diễn ra tại đây. obj là biến ngăn xếp được đặt thành tham chiếu đến ObjectObject được cấp phát trên heap. Ngăn xếp sẽ chỉ được xóa (bằng số học con trỏ ngăn xếp).

Nhưng có, bản thân số Object sẽ bị xóa bởi bộ sưu tập rác. Tất cả các đối tượng phân bổ heap đều phải chịu GC.

EDIT: Để trả lời câu hỏi cụ thể hơn của bạn, spec Java không đảm bảo thu bởi bất kỳ thời gian cụ thể (xem the JVM spec) (tất nhiên nó sẽ được thu thập sau sử dụng cuối cùng của nó). Vì vậy, nó chỉ có thể trả lời cho việc triển khai cụ thể.

CHỈNH SỬA: Làm rõ, mỗi nhận xét

+1

Nó không đảm bảo thời gian NHƯNG không có nghĩa là nó sẽ thu ngẫu nhiên đối tượng của bạn bất cứ khi nào ngay cả khi nó vẫn còn trong phạm vi;) – Paolo

+2

@Paolo - vấn đề ở đây là nó nằm ngoài phạm vi sau lần sử dụng cuối cùng (không phải khi nó rơi ra khỏi ngăn xếp) –

+0

Và optmisations có thể làm cho thời gian sử dụng cuối cùng đáng ngạc nhiên. –

1

Như tôi chắc chắn bạn đã biết, trong Bộ sưu tập và thu gọn Java không xác định. Tất cả những gì bạn có thể xác định trong trường hợp này là khi wrapđủ điều kiện để thu thập rác thải. Tôi giả sử bạn đang yêu cầu nếu wrap chỉ trở thành đủ điều kiện cho GC khi phương thức trả về (và wrap vượt quá phạm vi). Tôi nghĩ rằng một số JVM (ví dụ: HotSpot với -server) sẽ không chờ tham chiếu đối tượng được xuất hiện từ ngăn xếp, nó sẽ làm cho nó đủ điều kiện cho GC ngay sau khi không có gì khác tham chiếu đến nó. Có vẻ như đây là những gì bạn đang thấy.

Để tóm tắt, bạn đang dựa vào quyết toán đủ chậm để không hoàn tất phiên bản SqlConnection trước khi thoát khỏi phương pháp. Bạn finalizer là đóng một nguồn tài nguyên mà SqlConnection không còn chịu trách nhiệm. Thay vào đó, bạn nên để đối tượng Connection chịu trách nhiệm về quyết toán riêng của mình.

+0

@Ben Lings: thực sự phương thức close() được công khai là cho phép người dùng loại bỏ rõ ràng đối tượng. Tuy nhiên, nếu người dùng quên đóng() đối tượng, trình kết thúc hoạt động như người bảo vệ cuối cùng để giải phóng tài nguyên. Điều này tương tự như mô hình thiết kế vứt bỏ .NET. –

+0

Vấn đề là trình hoàn thiện của bạn đang dọn sạch tài nguyên mà nó không chịu trách nhiệm. –

5

Tôi thực sự ngạc nhiên về điều này, nhưng bạn nói đúng. Nó dễ dàng tái sản xuất, bạn không cần phải muck về với các kết nối cơ sở dữ liệu và các loại tương tự:

public class GcTest { 

    public static void main(String[] args) { 
     System.out.println("Starting"); 

     Object dummy = new GcTest(); // gets GC'd before method exits 

     // gets bigger and bigger until heap explodes 
     Collection<String> collection = new ArrayList<String>(); 

     // method never exits normally because of while loop 
     while (true) { 
      collection.add(new String("test")); 
     } 
    } 

    @Override 
    protected void finalize() throws Throwable { 
     System.out.println("Finalizing instance of GcTest"); 
    } 
} 

Chạy với:

Starting 
Finalizing instance of GcTest 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
    at java.util.Arrays.copyOf(Arrays.java:2760) 
    at java.util.Arrays.copyOf(Arrays.java:2734) 
    at java.util.ArrayList.ensureCapacity(ArrayList.java:167) 
    at java.util.ArrayList.add(ArrayList.java:351) 
    at test.GcTest.main(GcTest.java:22) 

Như tôi đã nói, tôi khó có thể tin được, nhưng không thể phủ nhận các bằng chứng.

Nó thực hiện một loại ý nghĩa nghịch đảo, mặc dù, VM sẽ tìm ra rằng đối tượng không bao giờ được sử dụng, và do đó được loại bỏ nó. Điều này phải được cho phép bởi thông số kỹ thuật.

Quay lại mã của câu hỏi, bạn không bao giờ nên dựa vào finalize() để xóa kết nối của mình, bạn nên luôn làm làm điều đó một cách rõ ràng.

+1

wow, thông tin chi tiết tuyệt vời. – Zaki

0

Theo thông số kỹ thuật hiện tại, thậm chí không có xảy ra trước khi đặt hàng từ việc hoàn thành để sử dụng bình thường. Vì vậy, để áp đặt trật tự, bạn thực sự cần phải sử dụng một khóa, một biến động hoặc, nếu bạn đang tuyệt vọng, stashing một tài liệu tham khảo có thể truy cập từ một tĩnh. Chắc chắn không có gì đặc biệt về phạm vi.

Rất hiếm khi bạn thực sự cần viết một finaliser.

1

obj có bị thu gom rác trước khi foo() trả về không?

Bạn không thể chắc chắn obj sẽ được thu thập trước khi foo lợi nhuận nhưng nó chắc chắn là đủ điều kiện cho việc thu thập trước khi foo lợi nhuận.

Có ai giải thích tại sao điều này xảy ra không?

Các GC thu thập các đối tượng không thể truy cập được. Đối tượng của bạn có khả năng trở nên không thể truy cập trước khi trả về foo.

Phạm vi không liên quan. Ý tưởng rằng obj vẫn ở trên ngăn xếp cho đến khi trả về foo là một mô hình tinh thần quá đơn giản. Các hệ thống thực không hoạt động như thế.

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