2012-09-17 31 views
5

Giả sử tôi đã tạo một lớp tài nguyên với phương thức close() để làm sạch tài nguyên và tôi muốn ghi đè xong() để giải phóng tài nguyên (và in một cảnh báo) nếu ai đó đã quên gọi close(). Làm thế nào điều này có thể được thực hiện đúng cách?Làm thế nào để thực hiện đúng một trình hoàn thiện để phát hiện rò rỉ tài nguyên trong Java

  • Chỉ được đề xuất cho tài nguyên gốc (được phân bổ bởi JNI)?
  • Điều gì sẽ xảy ra nếu bạn sử dụng tham chiếu đến một đối tượng khác đã được hoàn thành, từ trình kết thúc? Nếu có các phụ thuộc tuần hoàn, tôi không thấy cách trình thu gom rác có thể ngăn cản bạn truy cập vào các đối tượng mà các trình finalizers có thể đã được thực thi.
  • Có cách nào khác thay thế tốt hơn để ghi đè xong() để phát hiện và/hoặc xử lý rò rỉ tài nguyên không?
  • Bất kỳ cạm bẫy nào khác cần lưu ý khi triển khai trình kết thúc?

Lưu ý: Tôi biết rằng việc sử dụng finalize() thường là ý tưởng tồi và không đảm bảo được gọi, có một số câu hỏi khác thảo luận về điều này. Câu hỏi này đặc biệt về cách để triển khai trình kết xuất bằng Java, không phải về số lý do tại sao bạn nên (hoặc không nên).

+0

Tôi hy vọng câu trả lời của tôi sẽ hữu ích. Hãy cho tôi biết nếu tôi bỏ lỡ bất cứ điều gì. –

+0

Rephrased điểm thứ hai về việc truy cập các đối tượng có thể hoàn thành từ finalizer. Vẫn còn tò mò về những gì sẽ là tác động của điều đó. – Soulman

Trả lời

4

Trong effective java (2nd edition), Joshua nói chi tiết trong mụC# 7 về cách bạn có thể thực hiện việc này. Đầu tiên anh ta gợi ý rằng bạn hầu như không nên sử dụng finalizer s. Tuy nhiên, một lý do để sử dụng nó để chỉ in một bản ghi nhật ký nói rằng bạn có một rò rỉ tài nguyên. Ông nói rằng một trong những lợi nhuận rút ra để làm điều đó theo cách này là một người nào đó có thể mở rộng lớp học của bạn và không đúng cách gọi siêu finalizer. Vì vậy, ông đề nghị để làm một cái gì đó như thế này trong các lớp con:

// Manual finalizer chaining 
    @Override protected void finalize() throws Throwable { 
     try { 
      ... // Finalize subclass state 
     } finally { 
      super.finalize(); 
    } 
} 

Điều này là để đảm bảo rằng nếu có điều gì phá vỡ trong lớp hiện hành finally sẽ vẫn được gọi. Đây có thể là một giải pháp tồi vì nó phụ thuộc vào người đang phân lớp của bạn. Một giải pháp thay thế là sử dụng tệp đối tượng người giám hộ để thực hiện việc này. Điều này có dạng:

// Finalizer Guardian idiom 
    public class Foo { 
// Sole purpose of this object is to finalize outer Foo object 
     private final Object finalizerGuardian = new Object() { 
     @Override protected void finalize() throws Throwable { 
      ... // Finalize outer Foo object 
     } 
     }; 
     ... // Remainder omitted 
} 

Đây là phương pháp tiếp cận rõ ràng hơn vì bạn biết rằng không ai có thể ghi đè chức năng đó.

Cách được đề xuất để đóng tài nguyên vẫn đang triển khai Closeable và đảm bảo người dùng đóng cửa. Như Josuha gợi ý, bạn không nên thực hiện bất kỳ thao tác nhạy cảm nào theo phương thức finalize. JVM có thể chọn để chạy nó như đôi khi trong tương lai. Nếu bạn đang phụ thuộc vào phương pháp này để làm cam kết hoặc một cái gì đó quan trọng thì đó là một ý tưởng tồi.

1

Chỉ được đề xuất cho tài nguyên gốc (được phân bổ bởi JNI)?

No. Trường hợp sử dụng của bạn cũng là giá trị hợp lệ cho finalizers. Tôi có nghĩa là rò rỉ tài nguyên khai thác gỗ.

Điều gì sẽ xảy ra nếu bạn truy cập vào một đối tượng Java khác đã được hoàn thành , trong finalizer?

Nếu bạn vẫn có thể truy cập, nó chưa được hoàn thành. Hoặc có lẽ tôi đang thiếu một cái gì đó trong câu hỏi của bạn.

Tôi hiểu ngay bây giờ. Nó có thể là trường hợp cả A và B đều đủ điều kiện thu gom rác và có một tham chiếu giữa chúng. Nó sẽ không có vấn đề như mặc định finalize() không có gì. Nếu bạn viết cho cả hai đối tượng tùy chỉnh của mình, hãy sử dụng các phương thức finalize() để viết mã độc lập với thứ tự chúng được hoàn thành. Bạn cũng nên bảo vệ tham chiếu trở thành null vì đối tượng tương ứng có thể đã được thu gom rác.

Có phương án thay thế nào tốt hơn để ghi đè hoàn chỉnh() để phát hiện và/hoặc xử lý rò rỉ tài nguyên không?

Tôi nghĩ điều quan trọng nhất khi sử dụng finalizers là phát hiện và ghi nhật ký/cảnh báo về rò rỉ không xử lý. Và thời điểm tốt nhất để ghi lại sự rò rỉ này là trước khi đối tượng tài nguyên được thu thập rác. Vì vậy, finalizers là một sự phù hợp tự nhiên cho việc này.

Tôi muốn nhấn mạnh điều này: Tôi sẽ không sử dụng finalizers để đối phó với một lập trình viên quên đóng tài nguyên, nhưng để thông báo cho anh ta rằng anh ta cần sửa mã của mình.

Bất kỳ cạm bẫy nào khác cần lưu ý khi triển khai trình kết thúc?

Dường như cũng có hình phạt về hiệu suất với các đối tượng có trình kết thúc. Bạn nên làm một bài kiểm tra xem nó như thế nào cho bạn. Nếu có một hình phạt hiệu suất quan trọng tôi sẽ cố gắng tưởng tượng một cơ chế để sử dụng finalizers chỉ vào thời gian phát triển để đăng nhập rò rỉ tài nguyên.

Và cẩn thận khi kế thừa một đối tượng với finalizers theo đề xuất của Amir Raminfar.

Một điều nữa: hãy xem mã nguồn của [FileInputStream][1] hoặc [FileOutputStream][2] họ sử dụng trình tổng hợp vì cùng một lý do.

+0

"Nếu bạn vẫn có thể truy cập nó, nó không được hoàn thành. Hoặc có thể tôi đang thiếu một cái gì đó trong câu hỏi của bạn." Nhưng nếu đối tượng A có tham chiếu đến đối tượng B và B được hoàn thành trước A, thì A không thể truy cập B từ trình kết thúc của nó? – Soulman

+0

Đã chỉnh sửa bài đăng của tôi và lặp lại câu hỏi đó. – Soulman

+0

Tôi hiểu ngay bây giờ, đã cập nhật câu trả lời. – dcernahoschi