Tại sao bạn sẽ thực hiện quyết toán ()?


371

Tôi đã đọc qua rất nhiều câu hỏi về tân binh Java finalize()và thấy nó thật hoang mang khi không ai thực sự nói rõ rằng việc hoàn thiện () là một cách không đáng tin cậy để làm sạch tài nguyên. Tôi thấy ai đó nhận xét rằng họ sử dụng nó để dọn sạch các Kết nối, điều này thực sự đáng sợ vì cách duy nhất để đến gần với sự đảm bảo rằng Kết nối được đóng là thực hiện thử (bắt) cuối cùng.

Tôi không được học CS, nhưng tôi đã lập trình Java một cách chuyên nghiệp gần một thập kỷ nay và tôi chưa bao giờ thấy ai thực hiện finalize()trong một hệ thống sản xuất bao giờ. Điều này vẫn không có nghĩa là nó không có công dụng của nó, hoặc những người tôi đã làm việc cùng đã làm đúng.

Vì vậy, câu hỏi của tôi là, những trường hợp sử dụng nào có để thực hiện finalize()mà không thể xử lý đáng tin cậy hơn thông qua một quy trình hoặc cú pháp khác trong ngôn ngữ?

Vui lòng cung cấp các kịch bản cụ thể hoặc trải nghiệm của bạn, chỉ cần lặp lại một cuốn sách văn bản Java hoặc hoàn thành mục đích sử dụng là không đủ, vì đó không phải là mục đích của câu hỏi này.


Phương pháp HI Finalize
Sameer Kazi

2
Hầu hết các ứng dụng và mã thư viện sẽ không bao giờ sử dụng finalize(). Tuy nhiên, mã thư viện nền tảng , chẳng hạn như SocketInputStreamquản lý tài nguyên bản địa thay cho người gọi, để cố gắng giảm thiểu rủi ro rò rỉ tài nguyên (hoặc sử dụng các cơ chế tương đương, như PhantomReferenceđược thêm vào sau). Vì vậy, hệ sinh thái cần chúng , mặc dù 99.9999% các nhà phát triển sẽ không bao giờ viết một.
Brian Goetz

Câu trả lời:


231

Bạn có thể sử dụng nó làm điểm dừng cho một đối tượng chứa tài nguyên bên ngoài (socket, file, v.v.). Thực hiện một close()phương thức và tài liệu mà nó cần được gọi.

Triển khai finalize()để close()xử lý nếu bạn phát hiện ra nó chưa được thực hiện. Có thể với một cái gì đó bị bỏ đi stderrđể chỉ ra rằng bạn đang dọn dẹp sau khi một người gọi có lỗi.

Nó cung cấp thêm sự an toàn trong một tình huống đặc biệt / lỗi. Không phải mọi người gọi sẽ làm try {} finally {}công cụ chính xác mỗi lần. Đáng tiếc, nhưng đúng trong hầu hết các môi trường.

Tôi đồng ý rằng nó hiếm khi cần thiết. Và như các nhà bình luận chỉ ra, nó đi kèm với chi phí hoạt động. Chỉ sử dụng nếu bạn cần sự an toàn "vành đai và treo" đó trong một ứng dụng chạy dài.

Tôi thấy rằng như Java 9, Object.finalize()không được dùng nữa! Họ chỉ cho chúng tôi java.lang.ref.Cleanerjava.lang.ref.PhantomReferencenhư là sự thay thế.


44
Chỉ cần đảm bảo rằng một ngoại lệ không bao giờ được đưa ra khi hoàn thành (), hoặc trình thu gom rác sẽ không tiếp tục dọn dẹp đối tượng đó và bạn sẽ bị rò rỉ bộ nhớ.
skaffman

17
Hoàn thiện không được đảm bảo để được gọi, vì vậy đừng tin vào việc phát hành tài nguyên.
flazed

19
skaffman - Tôi không tin điều đó (ngoài một số triển khai JVM có lỗi). Từ javadoc Object.finalize (): Nếu một ngoại lệ chưa được phát hiện được ném bởi phương thức hoàn thiện, ngoại lệ sẽ bị bỏ qua và hoàn thành đối tượng đó chấm dứt.
John M

4
Đề xuất của bạn có thể che giấu lỗi của mọi người (không gọi gần) trong một thời gian dài. Tôi không khuyên bạn nên làm như vậy.
su hào

64
Tôi thực sự đã sử dụng hoàn thiện cho lý do chính xác này. Tôi đã kế thừa mã và nó rất có lỗi và có xu hướng để các kết nối cơ sở dữ liệu mở. Chúng tôi đã sửa đổi các kết nối để chứa dữ liệu về thời điểm và địa điểm chúng được tạo và sau đó triển khai hoàn thiện để ghi thông tin này nếu kết nối không được đóng đúng cách. Nó hóa ra là một trợ giúp tuyệt vời để theo dõi các kết nối không đóng.
brainimus

174

finalize()là một gợi ý cho JVM rằng có thể tốt khi thực thi mã của bạn tại một thời điểm không xác định. Điều này là tốt khi bạn muốn mã không chạy một cách bí ẩn.

Làm bất cứ điều gì có ý nghĩa trong việc hoàn thiện (về cơ bản là bất cứ điều gì ngoại trừ đăng nhập) cũng tốt trong ba tình huống:

  • bạn muốn đánh bạc rằng các đối tượng hoàn thiện khác sẽ vẫn ở trạng thái mà phần còn lại của chương trình của bạn coi là hợp lệ.
  • bạn muốn thêm nhiều mã kiểm tra vào tất cả các phương thức của tất cả các lớp của bạn có bộ hoàn thiện, để đảm bảo chúng hoạt động chính xác sau khi hoàn tất.
  • bạn muốn vô tình hồi sinh các đối tượng đã hoàn thành và dành nhiều thời gian cố gắng tìm hiểu tại sao chúng không hoạt động và / hoặc tại sao chúng không được hoàn thiện khi cuối cùng chúng được phát hành.

Nếu bạn nghĩ rằng bạn cần hoàn thiện (), đôi khi thứ bạn thực sự muốn là một tham chiếu ảo (trong ví dụ đưa ra có thể chứa một tham chiếu cứng đến một kết nối được sử dụng bởi giới thiệu của nó và đóng nó sau khi tham chiếu ảo được xếp hàng). Điều này cũng có thuộc tính mà nó có thể không bao giờ chạy một cách bí ẩn, nhưng ít nhất nó không thể gọi các phương thức trên hoặc phục hồi các đối tượng đã hoàn thành. Vì vậy, nó phù hợp với các tình huống mà bạn không nhất thiết phải đóng kết nối đó một cách sạch sẽ, nhưng bạn khá thích và các khách hàng trong lớp của bạn không thể hoặc không tự gọi mình (điều này thực sự đủ công bằng - gì' Điểm của việc có một bộ thu gom rác nếu bạn thiết kế các giao diện yêu cầu một hành động cụ thể được thực hiện trước khi thu thập? Điều đó chỉ đưa chúng ta trở lại trong thời của malloc / miễn phí.)

Những lần khác, bạn cần tài nguyên mà bạn nghĩ rằng bạn đang quản lý để mạnh mẽ hơn. Ví dụ, tại sao bạn cần phải đóng kết nối đó? Cuối cùng, nó phải dựa trên một số loại I / O do hệ thống cung cấp (ổ cắm, tệp, bất cứ thứ gì), vậy tại sao bạn không thể dựa vào hệ thống để đóng nó cho bạn khi mức tài nguyên thấp nhất bị ép? Nếu máy chủ ở đầu bên kia hoàn toàn yêu cầu bạn đóng kết nối sạch sẽ thay vì chỉ làm rơi ổ cắm, vậy thì điều gì sẽ xảy ra khi ai đó đi qua cáp nguồn của máy mà mã của bạn đang chạy hoặc mạng bị gián đoạn?

Tuyên bố miễn trừ trách nhiệm: Trước đây tôi đã từng thực hiện một triển khai JVM. Tôi ghét cuối cùng.


76
Ủng hộ cho công lý châm biếm cực đoan.
Nhận thức

32
Điều này không trả lời câu hỏi; nó chỉ nói lên tất cả những nhược điểm của việc finalize()không đưa ra bất kỳ lý do nào mà ai đó thực sự muốn sử dụng nó.
Ốc cơ khí

1
@supercat: tham chiếu ảo (đối với vấn đề đó tất cả các tham chiếu) đã được giới thiệu trong Java 2
Steve Jessop

1
@supercat: xin lỗi có, bởi "tài liệu tham khảo" Ý tôi là java.lang.ref.Referencevà các lớp con của nó. Java 1 đã có các biến :-) Và vâng, nó cũng có các bộ hoàn thiện.
Steve Jessop

2
@SteveJessop: Nếu một nhà xây dựng lớp cơ sở yêu cầu các thực thể khác thay mặt trạng thái của họ thay mặt cho đối tượng đang xây dựng (một mẫu rất phổ biến), nhưng một trình khởi tạo trường lớp dẫn xuất ném một ngoại lệ, đối tượng được xây dựng một phần sẽ ngay lập tức dọn sạch sau chính nó (tức là thông báo cho các thực thể bên ngoài đó, các dịch vụ của chúng không còn cần thiết nữa), nhưng cả Java và .NET đều không cung cấp bất kỳ mẫu làm sạch từ xa nào có thể xảy ra. Thật vậy, họ dường như đi ra ngoài để làm cho khó có thể tham khảo về điều cần dọn dẹp để đạt được mã dọn dẹp.
supercat

58

Một quy tắc đơn giản: không bao giờ sử dụng quyết toán.

Thực tế là một đối tượng có một bộ hoàn thiện (bất kể mã nào nó thực thi) là đủ để gây ra chi phí đáng kể cho việc thu gom rác.

Từ một bài viết của Brian Goetz:

Các đối tượng có bộ hoàn thiện (những đối tượng có phương thức quyết toán () không tầm thường) có chi phí đáng kể so với các đối tượng không có bộ hoàn thiện và nên được sử dụng một cách tiết kiệm. Đối tượng cuối cùng là cả chậm để phân bổ và chậm hơn để thu thập. Tại thời điểm phân bổ, JVM phải đăng ký bất kỳ đối tượng có thể hoàn thiện nào với trình thu gom rác và (ít nhất là trong triển khai JVM của HotSpot) phải theo một đường dẫn phân bổ chậm hơn hầu hết các đối tượng khác. Tương tự, các đối tượng cuối cùng cũng chậm để thu thập. Phải mất ít nhất hai chu kỳ thu gom rác (trong trường hợp tốt nhất) trước khi có thể thu hồi được một đối tượng cuối cùng và người thu gom rác phải thực hiện thêm công việc để gọi trình hoàn thiện. Kết quả là dành nhiều thời gian hơn cho việc phân bổ và thu thập các đối tượng và nhiều áp lực hơn đối với trình thu gom rác, bởi vì bộ nhớ được sử dụng bởi các đối tượng cuối cùng không thể truy cập được giữ lại lâu hơn. Kết hợp với thực tế là các công cụ hoàn thiện không được đảm bảo để chạy trong bất kỳ khung thời gian dự đoán nào, hoặc thậm chí, và bạn có thể thấy rằng có khá ít tình huống sử dụng công cụ phù hợp.


46

Lần duy nhất tôi đã sử dụng hoàn thiện trong mã sản xuất là để thực hiện kiểm tra xem tài nguyên của một đối tượng nhất định đã được dọn sạch chưa, và nếu không, thì hãy đăng nhập một thông điệp rất rõ ràng. Nó không thực sự cố gắng và tự làm điều đó, nó chỉ hét lên rất nhiều nếu nó không được thực hiện đúng cách. Hóa ra là khá hữu ích.


35

Tôi đã làm Java chuyên nghiệp từ năm 1998 và tôi chưa bao giờ thực hiện finalize(). Không chỉ một lần.


Làm thế nào để tôi phá hủy các đối tượng lớp công cộng sau đó? Nó gây ra vấn đề trong ADF. Về cơ bản, nó giả sử tải lại trang (lấy dữ liệu mới từ API), nhưng nó lấy đối tượng cũ và hiển thị kết quả
InfantPro'Aravind '

2
Kết thúc cuộc gọi @ InfantPro'Aravind 'không xóa đối tượng. Đó là một phương thức bạn triển khai mà JVM có thể chọn để gọi nếu và khi rác đó thu thập đối tượng của bạn.
Paul Tomblin

@PaulTomblin, ohk cảm ơn vì chi tiết đó. Bất kỳ đề nghị làm mới một trang trên ADF?
InfantPro'Aravind '

@ InfantPro'Aravind 'Không có ý tưởng, không bao giờ sử dụng ADF.
Paul Tomblin

28

Câu trả lời được chấp nhận là tốt, tôi chỉ muốn thêm rằng bây giờ có một cách để có chức năng hoàn thiện mà không thực sự sử dụng nó.

Nhìn vào các lớp "Tham khảo". Tham chiếu yếu, Tham chiếu Phantom & Tham chiếu mềm.

Bạn có thể sử dụng chúng để giữ một tham chiếu đến tất cả các đối tượng của mình, nhưng tài liệu tham khảo này sẽ không dừng lại ở GC. Điều thú vị về điều này là bạn có thể yêu cầu nó gọi một phương thức khi nó sẽ bị xóa và phương thức này có thể được đảm bảo để được gọi.

Về phần hoàn thiện: Tôi đã sử dụng quyết toán một lần để hiểu những gì các đối tượng đang được giải phóng. Bạn có thể chơi một số trò chơi gọn gàng với số liệu thống kê, đếm tham chiếu và những thứ tương tự - nhưng nó chỉ để phân tích, nhưng xem ra mã như thế này (không chỉ trong hoàn thiện, mà đó là nơi bạn có thể thấy nó nhất):

public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

Đó là một dấu hiệu cho thấy ai đó đã không biết những gì họ đang làm. "Dọn dẹp" như thế này hầu như không bao giờ cần thiết. Khi lớp là GC'd, điều này được thực hiện tự động.

Nếu bạn tìm thấy mã như thế trong một bản hoàn thiện thì chắc chắn rằng người viết nó đã bị nhầm lẫn.

Nếu nó ở nơi khác, có thể mã là một bản vá hợp lệ cho một mô hình xấu (một lớp tồn tại trong một thời gian dài và vì một số lý do, những thứ mà nó tham chiếu phải được giải phóng bằng tay trước khi đối tượng là GC'd). Nói chung là vì ai đó đã quên xóa người nghe hoặc một cái gì đó và không thể hiểu tại sao đối tượng của họ không phải là GC nên họ chỉ xóa những thứ mà nó đề cập và nhún vai và bỏ đi.

Nó không bao giờ nên được sử dụng để dọn dẹp mọi thứ "Nhanh hơn".


3
Tài liệu tham khảo Phantom là một cách tốt hơn để theo dõi những đối tượng nào đang được giải phóng, tôi nghĩ vậy.
Bill Michell

2
nâng cao để cung cấp lời giải thích tốt nhất về "tài liệu tham khảo yếu" mà tôi từng nghe.
sscarduzio

Tôi xin lỗi vì điều này đã khiến tôi mất rất nhiều năm để đọc nó, nhưng nó thực sự là một bổ sung tốt đẹp cho các câu trả lời.
Spencer Kormos

27

Tôi không chắc bạn có thể làm gì với thứ này, nhưng ...

itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41

Vì vậy, tôi đoán rằng Mặt trời đã tìm thấy một số trường hợp (họ nghĩ) nó nên được sử dụng.


12
Tôi đoán đó cũng là một câu hỏi của "Nhà phát triển Sun Sun sẽ luôn luôn đúng"?
CodeReaper

13
Nhưng có bao nhiêu trong số những sử dụng đó chỉ là triển khai hoặc thử nghiệm hỗ trợ finalizethay vì thực sự sử dụng nó?
Ốc cơ khí

Tôi sử dụng Windows. Làm thế nào bạn sẽ làm điều tương tự trong Windows?
gparyani

2
@damryfbfnetsi: Hãy thử cài đặt ack ( beyondgrep.com ) bằng cách của chocolatey.org/packages/ack . Hoặc sử dụng tính năng Tìm trong Tệp đơn giản trong $FAVORITE_IDE.
Brian Cline

21
class MyObject {
    Test main;

    public MyObject(Test t) {    
        main = t; 
    }

    protected void finalize() {
        main.ref = this; // let instance become reachable again
        System.out.println("This is finalize"); //test finalize run only once
    }
}

class Test {
    MyObject ref;

    public static void main(String[] args) {
        Test test = new Test();
        test.ref = new MyObject(test);
        test.ref = null; //MyObject become unreachable,finalize will be invoked
        System.gc(); 
        if (test.ref != null) System.out.println("MyObject still alive!");  
    }
}

====================================

kết quả:

This is finalize

MyObject still alive!

=====================================

Vì vậy, bạn có thể làm cho một thể hiện không thể truy cập được trong phương thức hoàn thiện.


21
Vì một số lý do, mã này khiến tôi nghĩ về phim zombie. Bạn GC đối tượng của bạn và sau đó nó đứng lên một lần nữa ...
steveha

ở trên là mã tốt. Tôi đã tự hỏi làm thế nào để làm cho đối tượng có thể tiếp cận lại được ngay bây giờ.
lwpro2

15
không, ở trên là mã xấu. đó là một ví dụ về lý do tại sao hoàn thiện là xấu.
Tony

Hãy nhớ rằng việc gọi System.gc();không phải là một đảm bảo rằng trình thu gom rác thực sự sẽ chạy. Đó là toàn quyền của GC để dọn dẹp đống (có thể vẫn còn đủ đống miễn phí xung quanh, có thể không bị phân mảnh thành nhiều, ...). Vì vậy, đó chỉ là một yêu cầu khiêm tốn của lập trình viên "làm ơn, bạn có thể nghe tôi nói và chạy không?" và không phải là một lệnh "hãy chạy ngay bây giờ như tôi nói!". Xin lỗi vì đã sử dụng các từ ngữ nhưng nó nói chính xác cách thức hoạt động của JVM.
Roland

10

finalize()có thể hữu ích để bắt rò rỉ tài nguyên. Nếu tài nguyên nên được đóng nhưng không ghi thực tế là nó không được đóng vào tệp nhật ký và đóng nó. Bằng cách đó, bạn loại bỏ rò rỉ tài nguyên và cung cấp cho mình một cách để biết rằng nó đã xảy ra để bạn có thể khắc phục nó.

Tôi đã lập trình bằng Java từ 1.0 alpha 3 (1995) và tôi vẫn chưa ghi đè hoàn thiện cho bất cứ điều gì ...


6

Bạn không nên phụ thuộc vào quyết toán () để dọn sạch tài nguyên cho bạn. hoàn thành () sẽ không chạy cho đến khi lớp được thu gom rác, nếu sau đó. Tốt hơn nhiều là sử dụng tài nguyên miễn phí một cách rõ ràng khi bạn sử dụng xong chúng.


5

Để làm nổi bật một điểm trong các câu trả lời ở trên: các công cụ hoàn thiện sẽ được thực hiện trên luồng đơn độc. Tôi đã nghe nói về một bản demo lớn của Sun, nơi các nhà phát triển đã thêm một giấc ngủ nhỏ vào một số người hoàn thiện và cố tình mang một bản demo 3D lạ mắt đến đầu gối của nó.

Tốt nhất nên tránh, ngoại trừ có thể có chẩn đoán test-env.

Suy nghĩ của Eckel trong Java có một phần tốt về điều này.


4

Hmmm, tôi đã từng sử dụng nó để dọn sạch các vật thể không được đưa trở lại một hồ bơi hiện có.

Họ đã được thông qua rất nhiều, vì vậy không thể biết khi nào họ có thể trở lại bể bơi một cách an toàn. Vấn đề là nó đã đưa ra một hình phạt rất lớn trong quá trình thu gom rác lớn hơn nhiều so với bất kỳ khoản tiết kiệm nào từ việc gộp các đối tượng. Nó đã được sản xuất khoảng một tháng trước khi tôi xé toạc toàn bộ hồ bơi, khiến mọi thứ trở nên năng động và được thực hiện với nó.


4

Hãy cẩn thận về những gì bạn làm trong một finalize() . Đặc biệt nếu bạn đang sử dụng nó cho những việc như gọi close () để đảm bảo rằng tài nguyên được dọn sạch. Chúng tôi đã gặp phải một số tình huống trong đó chúng tôi có các thư viện JNI được liên kết với mã java đang chạy và trong bất kỳ trường hợp nào chúng tôi sử dụng quyết toán () để gọi các phương thức JNI, chúng tôi sẽ bị hỏng heap java rất tệ. Tham nhũng không phải do chính mã JNI cơ bản gây ra, tất cả các dấu vết bộ nhớ đều ổn trong các thư viện riêng. Đó chỉ là thực tế rằng chúng tôi đã gọi các phương thức JNI từ hoàn thiện ().

Điều này là với JDK 1.5 vẫn đang được sử dụng rộng rãi.

Chúng tôi sẽ không phát hiện ra rằng có điều gì đó không ổn cho đến sau này, nhưng cuối cùng, thủ phạm luôn là phương thức hoàn thiện () sử dụng các cuộc gọi JNI.


3

Khi viết mã sẽ được sử dụng bởi các nhà phát triển khác yêu cầu một số phương thức "dọn dẹp" được gọi để giải phóng tài nguyên. Đôi khi những nhà phát triển khác quên gọi phương thức dọn dẹp của bạn (hoặc đóng, hoặc hủy hoặc bất cứ thứ gì). Để tránh rò rỉ tài nguyên có thể, bạn có thể kiểm tra phương thức hoàn thiện để đảm bảo rằng phương thức đó đã được gọi và nếu không, bạn có thể tự gọi nó.

Nhiều trình điều khiển cơ sở dữ liệu thực hiện điều này trong các triển khai Tuyên bố và Kết nối để cung cấp một chút an toàn chống lại các nhà phát triển quên gọi gần gũi với họ.


2

Chỉnh sửa: Được rồi, nó thực sự không hoạt động. Tôi đã thực hiện nó và nghĩ rằng đôi khi nó thất bại với tôi nhưng nó thậm chí không gọi phương thức hoàn thiện một lần duy nhất.

Tôi không phải là một lập trình viên chuyên nghiệp nhưng trong chương trình của tôi, tôi có một trường hợp mà tôi nghĩ là một ví dụ về một trường hợp tốt khi sử dụng Finalize (), đó là một bộ đệm ghi nội dung của nó vào đĩa trước khi nó bị phá hủy. Bởi vì không cần thiết phải thực thi mỗi lần phá hủy, nó chỉ tăng tốc chương trình của tôi, tôi hy vọng rằng tôi đã không làm sai.

@Override
public void finalize()
{
    try {saveCache();} catch (Exception e)  {e.printStackTrace();}
}

public void saveCache() throws FileNotFoundException, IOException
{
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
    out.writeObject(cache);
}

Tôi nghĩ rằng một bộ đệm (loại bạn sẽ xóa vào đĩa) là một ví dụ tốt. Và ngay cả khi hoàn thành không được gọi, không có mồ hôi.
foo

2

Nó có thể hữu ích để loại bỏ những thứ đã được thêm vào một địa điểm toàn cầu / tĩnh (không cần thiết) và cần phải được loại bỏ khi đối tượng được gỡ bỏ. Ví dụ:

    khoảng trống riêng tư addGlobalClickListener () {
        yếuAwtEventListener = new WeakAWTEventListener (cái này);

        Toolkit.getDefaultToolkit (). AddAWTEventListener (yếuAwtEventListener, AWTEvent.MOUSE_EVENT_MASK);
    }

    @Ghi đè
    bảo vệ void hoàn thiện () ném throwable {
        super.finalize ();

        if (yếuAwtEventListener! = null) {
            Toolkit.getDefaultToolkit (). RemoveAWTEventListener (yếuAwtEventListener);
        }
    }

Điều này cần những nỗ lực bổ sung, vì khi người nghe có một tham chiếu mạnh đến thisthể hiện được truyền cho nó trong hàm tạo, thì thể hiện đó sẽ không bao giờ trở nên không thể truy cập được, do đó finalize()sẽ không bao giờ dọn sạch. Mặt khác, nếu người nghe giữ một tham chiếu yếu đến đối tượng đó, như tên cho thấy, cấu trúc này có nguy cơ bị xóa sạch vào những lúc không nên. Vòng đời đối tượng không phải là công cụ phù hợp để thực hiện ngữ nghĩa ứng dụng.
Holger

0

iirc - bạn có thể sử dụng phương thức hoàn thiện như một phương tiện để thực hiện cơ chế gộp cho các tài nguyên đắt tiền - vì vậy họ cũng không nhận được GC.


0

Như một lưu ý phụ:

Một đối tượng ghi đè hoàn thiện () được xử lý đặc biệt bởi trình thu gom rác. Thông thường, một đối tượng bị phá hủy ngay lập tức trong chu kỳ thu thập sau khi đối tượng không còn trong phạm vi. Tuy nhiên, thay vào đó, các đối tượng hoàn thành được chuyển đến một hàng đợi, trong đó các luồng hoàn thiện riêng biệt sẽ thoát khỏi hàng đợi và chạy phương thức Finalize () trên mỗi đối tượng. Khi phương thức Finalize () kết thúc, cuối cùng đối tượng sẽ sẵn sàng để thu gom rác trong chu kỳ tiếp theo.

Nguồn: Finalize () không dùng nữa trên java-9


0

Các tài nguyên (Tệp, Ổ cắm, Luồng, v.v.) cần được đóng lại sau khi chúng tôi hoàn thành chúng. Họ thường có close()phương pháp mà chúng ta thường gọi trong finallyphần try-catchbáo cáo. Đôi khi finalize()cũng có thể được sử dụng bởi một vài nhà phát triển nhưng IMO không phải là một cách phù hợp vì không có gì đảm bảo rằng việc hoàn thiện sẽ luôn được gọi.

Trong Java 7, chúng ta đã có câu lệnh try-with-resource có thể được sử dụng như:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  // Processing and other logic here.
} catch (Exception e) {
  // log exception
} finally {
  // Just in case we need to do some stuff here.
}

Trong ví dụ trên, try-with-resource sẽ tự động đóng tài nguyên BufferedReaderbằng cách gọi close()phương thức. Nếu chúng ta muốn, chúng ta cũng có thể triển khai Closable trong các lớp của chính mình và sử dụng nó theo cách tương tự. IMO có vẻ gọn gàng và đơn giản hơn để hiểu.


0

Cá nhân, tôi hầu như không bao giờ sử dụng finalize()ngoại trừ trong một trường hợp hiếm hoi: Tôi đã thực hiện một bộ sưu tập kiểu chung tùy chỉnh và tôi đã viết một finalize()phương thức tùy chỉnh thực hiện như sau:

public void finalize() throws Throwable {
    super.finalize();
    if (destructiveFinalize) {
        T item;
        for (int i = 0, l = length(); i < l; i++) {
            item = get(i);
            if (item == null) {
                continue;
            }
            if (item instanceof Window) {
                ((Window) get(i)).dispose();
            }
            if (item instanceof CompleteObject) {
                ((CompleteObject) get(i)).finalize();
            }
            set(i, null);
        }
    }
}

( CompleteObjectLà một giao diện tôi đã thực hiện cho phép bạn xác định rằng bạn đã thực hiện hiếm khi thực hiện Objectcác phương pháp như #finalize(), #hashCode()#clone())

Vì vậy, bằng cách sử dụng một #setDestructivelyFinalizes(boolean)phương thức chị em , chương trình sử dụng bộ sưu tập của tôi có thể (giúp) đảm bảo rằng việc hủy tham chiếu đến bộ sưu tập này cũng phá hủy các tham chiếu đến nội dung của nó và loại bỏ bất kỳ cửa sổ nào có thể khiến JVM vô tình tồn tại. Tôi đã xem xét cũng dừng bất kỳ chủ đề nào, nhưng điều đó đã mở ra một lon giun hoàn toàn mới.


4
Kêu gọi finalize()trên của bạn CompleteObjectví dụ như bạn làm trong đoạn code trên ngụ ý rằng nó có thể được gọi là hai lần, như JVM vẫn có thể gọi finalize()một lần các đối tượng này thực sự đã trở thành không thể truy cập, trong đó, do sự logic của quyết toán, có thể là trước khi các finalize()phương pháp sưu tập đặc biệt của bạn được gọi hoặc thậm chí đồng thời cùng một lúc. Vì tôi không thấy bất kỳ biện pháp nào để đảm bảo an toàn luồng trong phương pháp của bạn, bạn dường như không biết gì về điều này
Holger

0

Câu trả lời được chấp nhận liệt kê rằng việc đóng tài nguyên trong quá trình hoàn thiện có thể được thực hiện.

Tuy nhiên câu trả lời này cho thấy rằng ít nhất trong java8 với trình biên dịch JIT, bạn gặp phải các sự cố không mong muốn khi đôi khi trình hoàn thiện được gọi ngay cả trước khi bạn đọc xong từ một luồng được duy trì bởi đối tượng của bạn.

Vì vậy, ngay cả trong tình huống đó, gọi là hoàn thiện sẽ không được khuyến khích.


Nó sẽ hoạt động, nếu hoạt động được thực hiện luồng an toàn, đó là điều bắt buộc, vì các công cụ hoàn thiện có thể được gọi bởi các luồng tùy ý. Do an toàn luồng được triển khai chính xác ngụ ý rằng không chỉ hoạt động gần, mà cả các hoạt động sử dụng tài nguyên phải đồng bộ hóa trên cùng một đối tượng hoặc khóa, sẽ có một mối quan hệ xảy ra trước khi sử dụng và hoạt động gần, cũng ngăn chặn quá sớm quyết toán. Nhưng, tất nhiên, với mức giá cao cho toàn bộ việc sử dụng,
Holger
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.