2008-08-26 33 views
27

Sau khi đọc this question, tôi đã được nhắc nhở khi tôi được dạy Java và nói không bao giờ gọi finalize() hoặc chạy bộ thu gom rác vì "đó là một hộp đen lớn mà bạn không bao giờ cần phải lo lắng về". Ai đó có thể đun sôi lý do cho điều này xuống một vài câu? Tôi chắc rằng tôi có thể đọc một báo cáo kỹ thuật từ Sun về vấn đề này, nhưng tôi nghĩ một câu trả lời ngắn gọn, đơn giản sẽ thỏa mãn sự tò mò của tôi.Tại sao bạn không gọi finalize() hoặc bắt đầu thu gom rác?

Trả lời

42

Câu trả lời ngắn: Bộ sưu tập rác Java là một công cụ được điều chỉnh rất tinh xảo. System.gc() là một cái búa tạ.

Heap của Java được chia thành các thế hệ khác nhau, mỗi thế hệ được thu thập bằng một chiến lược khác. Nếu bạn đính kèm một profiler vào một ứng dụng lành mạnh, bạn sẽ thấy rằng nó hiếm khi phải chạy các loại bộ sưu tập đắt nhất vì hầu hết các đối tượng đều bị bắt bởi bộ thu sao chép nhanh hơn trong thế hệ trẻ.

Hệ thống gọi điện thoại() trực tiếp, trong khi về mặt kỹ thuật không được bảo đảm để làm bất cứ điều gì, trên thực tế sẽ kích hoạt bộ sưu tập đống đầy đủ, đắt tiền trên thế giới. Đây là hầu như luôn luôn là điều sai trái để làm. Bạn nghĩ rằng bạn đang tiết kiệm tài nguyên, nhưng bạn đang thực sự lãng phí chúng không có lý do chính đáng, buộc Java phải kiểm tra lại tất cả các đối tượng trực tiếp của bạn "chỉ trong trường hợp".

Nếu bạn gặp sự cố với tạm dừng GC trong những khoảnh khắc quan trọng, bạn nên định cấu hình JVM để sử dụng bộ thu/quét đồng thời, được thiết kế đặc biệt để giảm thiểu thời gian bị tạm dừng. vấn đề và chỉ phá vỡ nó thêm.

Tài liệu Sun bạn đang nghĩ đến việc là ở đây: Java SE 6 HotSpot™ Virtual Machine Garbage Collection Tuning

(Một điều bạn có thể không biết: thực hiện một phương pháp hoàn thiện() trên đối tượng của bạn làm cho thu gom rác thải chậm Thứ nhất, nó sẽ mất hai GC. chạy để thu thập đối tượng: một để chạy finalize() và tiếp theo để đảm bảo rằng đối tượng không được phục hồi trong quá trình hoàn thành. được thu thập riêng lẻ, chúng không thể bị vứt bỏ hàng loạt.)

0

GC thực hiện tối ưu hóa rất nhiều khi nào cần hoàn thành đúng cách. Vì vậy, trừ khi bạn đã quen thuộc với cách GC thực sự hoạt động và cách nó tạo ra các thế hệ, gọi thủ công hoàn thành hoặc bắt đầu GC'ing có thể sẽ làm tổn thương hiệu suất hơn là giúp đỡ.

1

Giả sử finalizers là tương tự như tên .NET của chúng. Sau đó bạn chỉ thực sự cần phải gọi chúng khi bạn có các tài nguyên như xử lý tệp có thể bị rò rỉ. Hầu hết thời gian các đối tượng của bạn không có các tham chiếu này để chúng không cần phải được gọi.

Thật tệ khi cố gắng thu gom rác vì nó không thực sự là rác của bạn. Bạn đã yêu cầu máy ảo cấp phát một số bộ nhớ khi bạn tạo các đối tượng và trình thu thập rác đang ẩn thông tin về các đối tượng đó. Nội bộ GC đang thực hiện tối ưu hóa trên phân bổ bộ nhớ mà nó tạo ra. Khi bạn cố gắng thu thập rác một cách thủ công, bạn không có kiến ​​thức về những gì GC muốn giữ và thoát khỏi, bạn chỉ cần ép nó là tay. Kết quả là bạn mess up tính toán nội bộ.

Nếu bạn biết nhiều hơn về những gì GC đang nắm giữ trong nội bộ thì bạn có thể đưa ra quyết định sáng suốt hơn, nhưng sau đó bạn đã bỏ lỡ những lợi ích của GC.

4

Đừng bận tâm với finalizers.

Chuyển sang thu gom rác gia tăng.

Nếu bạn muốn trợ giúp bộ thu gom rác, hãy tắt tham chiếu đến các đối tượng bạn không cần nữa. Ít đường dẫn để theo dõi = rác thải rõ ràng hơn.

Đừng quên rằng các cá thể lớp bên trong (không tĩnh) giữ tham chiếu đến cá thể lớp cha của chúng. Vì vậy, một sợi lớp bên trong giữ hành lý nhiều hơn bạn có thể mong đợi. Trong một mạch rất có liên quan, nếu bạn đang sử dụng serialization, và bạn đã serialized các đối tượng tạm thời, bạn sẽ cần phải xóa cache tuần tự, bằng cách gọi ObjectOutputStream.reset() hoặc quá trình của bạn sẽ bị rò rỉ bộ nhớ và cuối cùng chết. Nhược điểm là các đối tượng không tạm thời sẽ được sắp xếp lại. Nối tiếp các đối tượng kết quả tạm thời có thể hơi lộn xộn hơn bạn nghĩ!

Cân nhắc sử dụng các tham chiếu mềm. Nếu bạn không biết tài liệu tham khảo mềm là gì, hãy đọc javadoc về java.lang.ref.SoftReference

Chỉ định xóa tham chiếu Phantom và Tham chiếu yếu trừ khi bạn thực sự bị kích động.

Cuối cùng, nếu bạn thực sự không thể chịu đựng được GC sử dụng Java thời gian thực.

Không, tôi không nói đùa.

Việc triển khai tham chiếu miễn phí để tải xuống và sách của Peter Dibbles từ SUN thực sự rất hay.

+0

Tôi muốn xem xét trường hợp sử dụng cho 'WeakReference' mạnh hơn nhiều so với trường hợp 'SoftReference'. Nếu 'Foo' cần tham chiếu đến' Bar' vì lợi ích của Foo, nó nên sử dụng một tham chiếu mạnh mẽ. Nếu 'Foo' cần tham chiếu đến' Bar' vì lợi ích của Bar, nó nên sử dụng một tham chiếu yếu. Ví dụ, 'Bar' có thể muốn được thông báo mỗi khi' Foo' làm điều gì đó, nhưng 'Foo' sẽ rất hạnh phúc nếu nó không phải thông báo cho bất cứ ai. Nếu các tham chiếu duy nhất tới 'Bar' được giữ bởi những thứ không thực sự quan tâm nếu nó tồn tại, thì nó sẽ không tồn tại. – supercat

0

Tránh finalizers. Không có gì đảm bảo rằng họ sẽ được gọi một cách kịp thời. Có thể mất khá nhiều thời gian trước khi hệ thống Quản lý bộ nhớ (tức là, bộ thu gom rác) quyết định thu thập đối tượng bằng trình hoàn thiện.

Nhiều người sử dụng finalizers để làm những việc như kết nối ổ cắm gần hoặc xóa các tệp tạm thời. Bằng cách đó, bạn làm cho hành vi ứng dụng của bạn không thể đoán trước được và gắn liền với khi JVM chuyển sang GC đối tượng của bạn. Điều này có thể dẫn đến các tình huống "hết bộ nhớ", không phải do Heap Java bị cạn kiệt, mà là do hệ thống đang chạy ra khỏi các chốt xử lý cho một tài nguyên cụ thể.

Một điều khác cần ghi nhớ là giới thiệu các cuộc gọi đến System.gc() hoặc búa như vậy có thể hiển thị kết quả tốt trong môi trường của bạn, nhưng chúng không nhất thiết phải dịch sang các hệ thống khác. Không phải tất cả mọi người đều chạy cùng một JVM, có rất nhiều, SUN, IBM J9, BEA JRockit, Harmony, OpenJDK, vv ... Tất cả các JVM này đều tuân thủ JCK (những người đã được kiểm tra chính thức), nhưng có rất nhiều tự do khi nói đến việc làm mọi thứ nhanh chóng. GC là một trong những lĩnh vực mà mọi người đầu tư vào rất nhiều. Sử dụng một cái búa thường sẽ phá hủy nỗ lực đó.

3

As far as finalizers đi:

  1. Họ hầu như vô dụng. Họ không được đảm bảo để được gọi một cách kịp thời, hoặc thực sự, ở tất cả (nếu GC không bao giờ chạy, sẽ không có bất kỳ finalizers). Điều này có nghĩa là bạn thường không nên dựa vào chúng.
  2. Người kết thúc không được đảm bảo là không có giá trị. Các nhà sưu tập rác thải rất cẩn thận để đảm bảo rằng nó sẽ không bao giờ gọi finalize() nhiều hơn một lần trên cùng một đối tượng. Với các đối tượng được viết tốt, nó sẽ không thành vấn đề, nhưng với các đối tượng kém bằng văn bản, việc gọi kết thúc nhiều lần có thể gây ra sự cố (ví dụ: phát hành gấp đôi tài nguyên gốc ... sự cố).
  3. Mọi đối tượng có phương thức finalize() cũng phải cung cấp phương thức close() (hoặc tương tự). Đây là chức năng bạn nên gọi. ví dụ: FileInputStream.close(). Không có lý do gì để gọi số finalize() khi bạn có phương thức phù hợp hơn mà được dự định sẽ được bạn gọi.
0

bạn không gọi phương thức hoàn tất.

1

Vấn đề thực sự với việc đóng các điều khiển hệ điều hành trong quá trình hoàn thành là việc hoàn thành được thực thi không theo thứ tự được đảm bảo. Nhưng nếu bạn có các xử lý đối với những thứ chặn (suy nghĩ ví dụ: ổ cắm) có khả năng mã của bạn có thể rơi vào tình trạng bế tắc (không quan trọng chút nào).

Vì vậy, tôi sẽ đóng một cách rõ ràng các cách xử lý theo thứ tự có thể dự đoán được. Về cơ bản mã cho đối phó với nguồn lực nên làm theo các mô hình:

SomeStream s = null; 
... 
try{ 
    s = openStream(); 
    .... 
    s.io(); 
    ... 
} finally { 
    if (s != null) { 
     s.close(); 
     s = null; 
    } 
} 

Nó được thậm chí phức tạp hơn nếu bạn viết các lớp học của riêng bạn mà làm việc thông qua JNI và mở xử lý. Bạn cần phải chắc chắn rằng các chốt được đóng (phát hành) và nó sẽ chỉ xảy ra một lần. Xử lý hệ điều hành thường bị bỏ qua trong Desktop J2SE là Graphics[2D]. Ngay cả BufferedImage.getGrpahics() cũng có khả năng trả lại cho bạn tay cầm trỏ vào trình điều khiển video (thực sự đang giữ tài nguyên trên GPU). Nếu bạn không tự giải phóng nó và để nó thu gom rác để thực hiện công việc - bạn có thể thấy tình huống OutOfMemory và tương tự lạ khi bạn chạy ra khỏi bitmap ánh xạ thẻ video nhưng vẫn còn nhiều bộ nhớ. Theo kinh nghiệm của tôi nó xảy ra khá thường xuyên trong các vòng chặt chẽ làm việc với các đối tượng đồ họa (giải nén hình thu nhỏ, mở rộng quy mô, làm sắc nét bạn đặt tên cho nó).

Về cơ bản GC không quản lý trách nhiệm của người lập trình về quản lý tài nguyên chính xác. Nó chỉ chăm sóc trí nhớ và không có gì khác. Các Stream.finalize gọi gần() IMHO sẽ được thực hiện tốt hơn ném ngoại lệ mới RuntimeError ("rác thu thập dòng đó vẫn còn mở"). Nó sẽ tiết kiệm giờ và ngày gỡ lỗi và làm sạch mã sau khi những người nghiệp dư cẩu thả rời khỏi đầu bị mất.

Mã hóa vui vẻ.

Hòa bình.

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