Khi nào phương thức Finalize () được gọi trong Java?


330

Tôi cần biết khi finalize()phương thức được gọi trong JVM. Tôi đã tạo một lớp kiểm tra ghi vào một tệp khi finalize()phương thức được gọi bằng cách ghi đè nó. Nó không được thực thi. Bất cứ ai có thể cho tôi biết lý do tại sao nó không được thực thi?


34
Cũng như một ghi chú bên lề: quyết toán được đánh dấu là không dùng nữa trong Java 9
Reg


nếu bất cứ điều gì có một tham chiếu đến đối tượng của bạn hoặc thậm chí cả lớp. finalize()và thu gom rác không có bất kỳ tác động nào.
ha9u63ar

Câu trả lời:


273

Nói chung, tốt nhất không nên dựa vào finalize()để làm bất kỳ việc dọn dẹp, vv

Theo Javadoc (đáng để đọc), đó là:

Được gọi bởi trình thu gom rác trên một đối tượng khi bộ sưu tập rác xác định rằng không có thêm tham chiếu đến đối tượng.

Như Joachim đã chỉ ra, điều này có thể không bao giờ xảy ra trong cuộc đời của một chương trình nếu đối tượng luôn có thể truy cập được.

Ngoài ra, trình thu gom rác không được đảm bảo để chạy bất cứ lúc nào. Nói chung, những gì tôi đang cố gắng nói finalize()có lẽ không phải là phương pháp tốt nhất để sử dụng nói chung trừ khi có một cái gì đó cụ thể mà bạn cần nó.


19
Nói cách khác (chỉ để làm rõ cho độc giả tương lai) nó không bao giờ được gọi trên lớp chính, vì, khi lớp chính đóng cửa, không cần phải thu gom rác. Hệ điều hành sẽ dọn sạch mọi thứ mà ứng dụng sử dụng.
Mark Jeronimus

113
"không phải là phương pháp tốt nhất để sử dụng ... trừ khi có thứ gì đó cụ thể mà bạn cần nó" - uh, câu đó áp dụng cho 100% mọi thứ, vì vậy không hữu ích. Câu trả lời của Joachimerer tốt hơn nhiều
BT

1
@ Zom-B ví dụ của bạn rất hữu ích để làm rõ, nhưng chỉ là mang tính mô phạm, có lẽ nó có thể được gọi trên lớp chính nếu lớp chính tạo ra một luồng không phải daemon và sau đó trả về?
Tom G

3
Vì vậy, những tình huống finalizesẽ hữu ích là gì?
nbro

3
@MarkJeronimus - Thật ra, điều đó không liên quan. Các finalize()phương pháp trên cho những lớp học chính được gọi khi một >> dụ << của lớp được thu thập rác thải, không phải khi chấm dứt phương pháp chính. Và bên cạnh đó, lớp chính có thể là rác được thu thập trước khi ứng dụng kết thúc; ví dụ: trong một ứng dụng đa luồng trong đó luồng "chính" tạo các luồng khác và sau đó trả về. (Trong thực tế, sẽ cần một trình nạp lớp không chuẩn ....)
Stephen C

379

Các finalizephương pháp được gọi khi một đối tượng là về để thu gom rác thải. Đó có thể là bất cứ lúc nào sau khi nó trở thành đủ điều kiện để thu gom rác.

Lưu ý rằng hoàn toàn có thể một đối tượng không bao giờ được thu gom rác (và do đó finalizekhông bao giờ được gọi). Điều này có thể xảy ra khi đối tượng không bao giờ đủ điều kiện cho gc (vì nó có thể truy cập trong toàn bộ thời gian tồn tại của JVM) hoặc khi không có bộ sưu tập rác nào thực sự chạy giữa thời gian đối tượng đủ điều kiện và thời gian JVM ngừng chạy (điều này thường xảy ra với đơn giản chương trình kiểm tra).

Có nhiều cách để bảo JVM chạy finalizetrên các đối tượng mà nó chưa được gọi, nhưng sử dụng chúng cũng không phải là một ý tưởng hay (sự đảm bảo của phương thức đó cũng không mạnh lắm).

Nếu bạn dựa vào finalizehoạt động chính xác của ứng dụng của mình, thì bạn đã làm sai điều gì đó. finalizenên chỉ được sử dụng cho dọn dẹp của (thường là phi Java) nguồn lực. Và đó chính xác là vì JVM không đảm bảo finalizeđược gọi cho bất kỳ đối tượng nào.


2
@Rajesh. Không. Đây không phải là vấn đề "tuổi thọ". Bạn có thể đặt chương trình của mình trong một vòng lặp vô hạn (trong nhiều năm) và nếu không cần bộ thu gom rác, nó sẽ không bao giờ chạy.
ChrisCantrell

5
@VikasVerma: sự thay thế hoàn hảo là không có gì: bạn không cần chúng. Trường hợp duy nhất có ý nghĩa là nếu lớp của bạn quản lý một số tài nguyên bên ngoài (như kết nối TCP / IP, tệp ... bất cứ thứ gì mà Java GC không thể xử lý). Trong những trường hợp đó, Closablegiao diện (và ý tưởng đằng sau nó) có thể là những gì bạn muốn: thực hiện .close()đóng / loại bỏ tài nguyên và yêu cầu người dùng của lớp bạn gọi nó vào đúng thời điểm. Bạn có thể muốn thêm một finalizephương thức "chỉ để lưu", nhưng đó sẽ là một công cụ gỡ lỗi nhiều hơn là một sửa chữa thực tế (vì nó không đủ tin cậy).
Joachim Sauer

4
@Dragonborn: đó thực sự là một câu hỏi khá khác biệt và nên được hỏi riêng. Có các móc tắt máy, nhưng chúng không được bảo đảm nếu JVM tắt đột ngột (còn gọi là sự cố). Nhưng sự đảm bảo của họ mạnh hơn đáng kể so với những người quyết định (và họ cũng an toàn hơn).
Joachim Sauer

4
Đoạn cuối của bạn nói rằng chỉ sử dụng nó để dọn dẹp tài nguyên mặc dù không có gì đảm bảo nó sẽ được gọi. Là sự lạc quan này nói? Tôi sẽ cho rằng một cái gì đó không đáng tin cậy vì điều này cũng sẽ không phù hợp để làm sạch tài nguyên.
Jeroen Vannevel

2
Người hoàn thiện đôi khi có thể tiết kiệm trong ngày ... Tôi đã gặp trường hợp thư viện bên thứ 3 đang sử dụng FileInputStream, không bao giờ đóng nó. Mã của tôi đã gọi mã của thư viện, và sau đó cố gắng di chuyển tệp, không thành công vì nó vẫn mở. Tôi đã buộc System.gc()phải gọi để gọi FileInputStream :: Finalize (), sau đó tôi có thể di chuyển tệp.
Elist

73
protected void finalize() throws Throwable {}
  • mỗi lớp kế thừa finalize()phương thức từ java.lang.Object
  • phương thức được gọi bởi trình thu gom rác khi nó xác định không còn tham chiếu đến đối tượng tồn tại
  • phương thức hoàn thiện đối tượng thực hiện không có hành động nào nhưng nó có thể bị ghi đè bởi bất kỳ lớp nào
  • thông thường, nó nên được ghi đè để dọn sạch các tài nguyên không phải Java, tức là đóng tệp
  • nếu ghi đè, finalize()đó là cách thực hành lập trình tốt để sử dụng câu lệnh try-Catch-cuối cùng và luôn gọi super.finalize(). Đây là một biện pháp an toàn để đảm bảo bạn không vô tình bỏ lỡ việc đóng tài nguyên được sử dụng bởi các đối tượng gọi lớp

    protected void finalize() throws Throwable {
         try {
             close();        // close open files
         } finally {
             super.finalize();
         }
     }
  • bất kỳ ngoại lệ nào được đưa ra finalize()trong quá trình thu gom rác đều dừng việc hoàn thiện nhưng lại bị bỏ qua

  • finalize() không bao giờ chạy nhiều hơn một lần trên bất kỳ đối tượng nào

trích dẫn từ: http://www.janeg.ca/scjp/gc/finalize.html

Bạn cũng có thể kiểm tra bài viết này:


25
Bài viết JavaWorld mà bạn liên kết đến từ năm 1998 và có một số lời khuyên thú vị, đặc biệt là gợi ý rằng một người gọi System.runFinalulatorsOnExit () để đảm bảo các trình hoàn thiện chạy trước khi JVM thoát. Phương pháp đó hiện không được chấp nhận, với nhận xét 'Phương pháp này vốn không an toàn. Nó có thể dẫn đến việc người hoàn thiện được gọi vào các đối tượng trực tiếp trong khi các luồng khác đang thao túng đồng thời các đối tượng đó, dẫn đến hành vi thất thường hoặc bế tắc. ' Vì vậy, tôi sẽ không làm điều đó.
Greg Chabala

3
Vì runFinalizerOnExit () KHÔNG an toàn cho luồng, nên những gì người ta có thể làm là Runtime.getR.78 (). AddShutdownHook (new Thread () {public void run () {killMyEnclosesClass ();}}); trong hàm tạo của Class.
Ustaman Sangat

2
@Ustaman Sangat Đó là một cách để làm điều đó, nhưng hãy nhớ điều này đặt tham chiếu đến cá thể của bạn từ shutdownHook, điều này đảm bảo khá nhiều rằng lớp của bạn không bao giờ được thu gom rác. Nói cách khác, đó là một rò rỉ bộ nhớ.
pieroxy

1
@pieroxy, trong khi tôi đồng ý mọi người khác ở đây về việc không sử dụng quyết toán () cho bất cứ điều gì, tôi không hiểu tại sao phải có một tài liệu tham khảo từ móc tắt máy. Người ta có thể có một tài liệu tham khảo mềm.
Ustaman Sangat

25

finalize()Phương thức Java không phải là hàm hủy và không nên được sử dụng để xử lý logic mà ứng dụng của bạn phụ thuộc vào. Thông số kỹ thuật Java tuyên bố không có gì đảm bảo rằng finalizephương thức này được gọi hoàn toàn trong thời gian tồn tại của ứng dụng.

Những gì bạn muốn là một sự kết hợp finallyvà một phương pháp dọn dẹp, như trong:

MyClass myObj;

try {
    myObj = new MyClass();

    // ...
} finally {

    if (null != myObj) {
        myObj.cleanup();
    }
}

20

Kiểm tra Java hiệu quả, ấn bản 2 trang 27. Mục 7: Tránh hoàn thiện

Quyết toán là không thể đoán trước, thường nguy hiểm, và nói chung là không cần thiết. không bao giờ làm bất cứ điều gì quan trọng thời gian trong một quyết toán. không bao giờ phụ thuộc vào một bộ hoàn thiện để cập nhật trạng thái liên tục quan trọng.

Để chấm dứt tài nguyên, thay vào đó hãy sử dụng thử:

// try-finally block guarantees execution of termination method
Foo foo = new Foo(...);
try {
    // Do what must be done with foo
    ...
} finally {
    foo.terminate(); // Explicit termination method
}

3
hoặc sử dụng tài nguyên thử
Gabriel Garcia

3
Điều này giả định tuổi thọ của một đối tượng nằm trong phạm vi của một chức năng. Tất nhiên, đó không phải là trường hợp mà OP đang đề cập, cũng không phải là trường hợp mà bất cứ ai cũng cần. Hãy suy nghĩ "bộ đệm trả về giá trị tham chiếu giá trị". Bạn muốn giải phóng mục nhập bộ đệm khi lần giới thiệu cuối cùng được giải phóng, nhưng bạn không biết khi nào lần giới thiệu cuối cùng được giải phóng. Finalize () có thể giảm số lượng ref, ví dụ ... nhưng nếu bạn yêu cầu người dùng của mình gọi một hàm miễn phí, bạn sẽ yêu cầu rò rỉ bộ nhớ. thông thường tôi chỉ làm cả hai (chức năng phát hành + kiểm tra hai lần hoàn tất ...) ...
Erik Aronesty

16

Khi nào finalize()phương thức được gọi trong Java?

Phương thức hoàn thiện sẽ được gọi sau khi GC phát hiện ra rằng đối tượng không còn có thể truy cập được nữa và trước khi nó thực sự lấy lại bộ nhớ mà đối tượng sử dụng.

  • Nếu một đối tượng không bao giờ trở nên không thể truy cập, finalize()sẽ không bao giờ được gọi trên đó.

  • Nếu GC không chạy thì finalize()có thể không bao giờ được gọi. (Thông thường, GC chỉ chạy khi JVM quyết định rằng có khả năng đủ rác để làm cho nó có giá trị.)

  • Có thể mất nhiều hơn một chu kỳ GC trước khi GC xác định rằng một đối tượng cụ thể là không thể truy cập được. (Các Java Java thường là các bộ sưu tập "thế hệ" ...)

  • Một khi GC phát hiện một đối tượng là không thể truy cập và hoàn thiện, nó sẽ nằm trên hàng đợi hoàn thiện. Hoàn thiện thường xảy ra không đồng bộ với GC bình thường.

(Thông số JVM thực sự cho phép JVM không bao giờ chạy bộ hoàn thiện ... miễn là nó không lấy lại không gian được sử dụng bởi các đối tượng. Một JVM được triển khai theo cách này sẽ bị tê liệt / vô dụng, nhưng hành vi này là "được phép" .)

Kết quả cuối cùng là không khôn ngoan khi dựa vào quyết toán để làm những việc phải làm trong một khung thời gian xác định. Đó là "thực hành tốt nhất" không sử dụng chúng cả. Cần có một cách tốt hơn (tức là đáng tin cậy hơn) để làm bất cứ điều gì mà bạn đang cố gắng làm trong finalize()phương pháp.

Việc sử dụng hợp pháp duy nhất để hoàn thiện là dọn sạch các tài nguyên liên quan đến các đối tượng đã bị mất bởi mã ứng dụng. Thậm chí sau đó, bạn nên cố gắng viết mã ứng dụng để nó không bị mất các đối tượng ở vị trí đầu tiên. (Ví dụ: sử dụng Java 7+ tài nguyên dùng thử để đảm bảo close()luôn được gọi là ...)


Tôi đã tạo một lớp kiểm tra ghi vào một tệp khi phương thức Finalize () được gọi bằng cách ghi đè nó. Nó không được thực thi. Bất cứ ai có thể cho tôi biết lý do tại sao nó không được thực thi?

Thật khó để nói, nhưng có một vài khả năng:

  • Đối tượng không phải là rác được thu thập bởi vì nó vẫn có thể truy cập.
  • Đối tượng không phải là rác được thu thập bởi vì GC không chạy trước khi thử nghiệm của bạn kết thúc.
  • Đối tượng được tìm thấy bởi GC và được xếp vào hàng đợi quyết toán bởi GC, nhưng việc hoàn thành không hoàn thành trước khi thử nghiệm của bạn kết thúc.

10

Do không có sự không chắc chắn trong việc gọi phương thức Finalize () bằng JVM (không chắc chắn liệu Finalize () bị ghi đè có được thực thi hay không), vì mục đích nghiên cứu, cách tốt hơn để quan sát những gì xảy ra khi gọi Final () buộc JVM gọi bộ sưu tập rác bằng lệnh System.gc().

Cụ thể, Finalize () được gọi khi một đối tượng không còn được sử dụng. Nhưng khi chúng ta cố gắng gọi nó bằng cách tạo các đối tượng mới thì không có gì chắc chắn về cuộc gọi của nó. Vì vậy, để chắc chắn, chúng tôi tạo ra một nullđối tượng crõ ràng không có sử dụng trong tương lai, do đó chúng tôi thấy đối tượngc cuộc gọi hoàn thiện của .

Thí dụ

class Car {

    int maxspeed;

    Car() {
        maxspeed = 70;
    }

    protected void finalize() {

    // Originally finalize method does nothing, but here we override finalize() saying it to print some stmt
    // Calling of finalize is uncertain. Difficult to observe so we force JVM to call it by System.gc(); GarbageCollection

        System.out.println("Called finalize method in class Car...");
    }
}

class Bike {

    int maxspeed;

    Bike() {
        maxspeed = 50;
    }

    protected void finalize() {
        System.out.println("Called finalize method in class Bike...");
    }
}

class Example {

    public static void main(String args[]) {
        Car c = new Car();
        c = null;    // if c weren`t null JVM wouldn't be certain it's cleared or not, null means has no future use or no longer in use hence clears it
        Bike b = new Bike();
        System.gc();    // should clear c, but not b
        for (b.maxspeed = 1; b.maxspeed <= 70; b.maxspeed++) {
            System.out.print("\t" + b.maxspeed);
            if (b.maxspeed > 50) {
                System.out.println("Over Speed. Pls slow down.");
            }
        }
    }
}

Đầu ra

    Called finalize method in class Car...
            1       2       3       4       5       6       7       8       9
    10      11      12      13      14      15      16      17      18      19
    20      21      22      23      24      25      26      27      28      29
    30      31      32      33      34      35      36      37      38      39
    40      41      42      43      44      45      46      47      48      49
    50      51Over Speed. Pls slow down.
            52Over Speed. Pls slow down.
            53Over Speed. Pls slow down.
            54Over Speed. Pls slow down.
            55Over Speed. Pls slow down.
            56Over Speed. Pls slow down.
            57Over Speed. Pls slow down.
            58Over Speed. Pls slow down. 
            59Over Speed. Pls slow down.
            60Over Speed. Pls slow down.
            61Over Speed. Pls slow down.
            62Over Speed. Pls slow down.
            63Over Speed. Pls slow down.
            64Over Speed. Pls slow down.
            65Over Speed. Pls slow down.
            66Over Speed. Pls slow down.
            67Over Speed. Pls slow down.
            68Over Speed. Pls slow down.
            69Over Speed. Pls slow down.
            70Over Speed. Pls slow down.

Lưu ý - Ngay cả sau khi in tới 70 và sau đó đối tượng b không được sử dụng trong chương trình, không chắc chắn rằng b có bị xóa hay không bởi JVM do "Phương thức gọi là hoàn thiện trong lớp Xe đạp ..." không được in.


10
Gọi điện System.gc();không đảm bảo rằng bộ sưu tập rác sẽ thực sự được chạy.
Simon Forsberg

3
Nó cũng không được đảm bảo loại bộ sưu tập sẽ được chạy. Có liên quan vì hầu hết các Java Java là các bộ sưu tập "thế hệ".
Stephen C

5

hoàn thiện sẽ in ra số lượng để tạo lớp.

protected void finalize() throws Throwable {
    System.out.println("Run F" );
    if ( checkedOut)
        System.out.println("Error: Checked out");
        System.out.println("Class Create Count: " + classCreate);
}

chủ yếu

while ( true) {
    Book novel=new Book(true);
    //System.out.println(novel.checkedOut);
    //Runtime.getRuntime().runFinalization();
    novel.checkIn();
    new Book(true);
    //System.runFinalization();
    System.gc();

Bạn có thể thấy. Việc đặt ra sau đây cho thấy gc đã được thực hiện lần đầu tiên khi số lớp là 36.

C:\javaCode\firstClass>java TerminationCondition
Run F
Error: Checked out
Class Create Count: 36
Run F
Error: Checked out
Class Create Count: 48
Run F

4

Gần đây đã phải vật lộn với các phương thức hoàn thiện (để loại bỏ các nhóm kết nối trong quá trình thử nghiệm), tôi phải nói rằng bộ hoàn thiện thiếu nhiều thứ. Sử dụng VisualVM để quan sát cũng như sử dụng các tham chiếu yếu để theo dõi tương tác thực tế tôi thấy rằng những điều sau đây là đúng trong môi trường Java 8 (Oracle JDK, Ubuntu 15):

  • Finalize không được gọi ngay lập tức Finalizer (phần GC) sở hữu tài liệu tham khảo một cách khó nắm bắt
  • Bộ sưu tập rác mặc định chứa các đối tượng không thể truy cập
  • Hoàn thiện được gọi hàng loạt chỉ vào một chi tiết thực hiện rằng có một giai đoạn nhất định mà trình thu gom rác giải phóng các tài nguyên.
  • Việc gọi System.gc () thường không dẫn đến việc các đối tượng được hoàn thiện thường xuyên hơn, điều đó chỉ khiến cho Trình hoàn thiện nhận thức được một đối tượng không thể truy cập nhanh hơn
  • Tạo kết xuất luồng hầu như luôn luôn dẫn đến việc kích hoạt bộ hoàn thiện do chi phí heap cao trong khi thực hiện kết xuất heap hoặc một số cơ chế nội bộ khác
  • Các đường nối hoàn thiện bị ràng buộc bởi một trong hai yêu cầu bộ nhớ (giải phóng thêm bộ nhớ) hoặc bởi danh sách các đối tượng được đánh dấu để hoàn thiện tăng giới hạn nội bộ nhất định. Vì vậy, nếu bạn có nhiều đối tượng được hoàn thiện, giai đoạn hoàn thiện sẽ được kích hoạt thường xuyên hơn và sớm hơn khi so sánh với chỉ một vài
  • Có những trường hợp System.gc () đã kích hoạt hoàn thiện trực tiếp nhưng chỉ khi tham chiếu là địa phương và sống ngắn. Điều này có thể liên quan đến thế hệ.

Suy nghĩ cuối cùng

Phương pháp hoàn thiện là không đáng tin cậy nhưng chỉ có thể được sử dụng cho một thứ. Bạn có thể đảm bảo rằng một đối tượng đã được đóng hoặc xử lý trước khi rác được thu gom để có thể thực hiện một sự cố an toàn nếu các đối tượng có vòng đời phức tạp hơn liên quan đến hành động cuối đời được xử lý chính xác. Đó là một lý do tôi có thể nghĩ về điều đó làm cho nó có giá trị để ghi đè lên nó.


2

Đối tượng trở thành đủ điều kiện cho bộ sưu tập Rác hoặc GC nếu không thể truy cập được từ bất kỳ luồng trực tiếp hoặc bất kỳ sự điều chỉnh tĩnh nào theo cách khác mà bạn có thể nói rằng một đối tượng trở thành đủ điều kiện để thu gom rác nếu tất cả các tham chiếu của nó là null. Các phụ thuộc theo chu kỳ không được tính là tham chiếu vì vậy nếu Đối tượng A có tham chiếu của đối tượng B và đối tượng B có tham chiếu của Đối tượng A và họ không có bất kỳ tham chiếu trực tiếp nào khác thì cả Đối tượng A và B sẽ đủ điều kiện để thu gom Rác. Nói chung, một đối tượng trở thành đủ điều kiện để thu gom rác trong Java trong các trường hợp sau:

  1. Tất cả các tham chiếu của đối tượng đó được đặt rõ ràng thành null, ví dụ: object = null
  2. Đối tượng được tạo bên trong một khối và tham chiếu đi ra ngoài phạm vi một khi điều khiển thoát khỏi khối đó.
  3. Đối tượng cha mẹ được đặt thành null, nếu một đối tượng giữ tham chiếu của đối tượng khác và khi bạn đặt tham chiếu đối tượng chứa null, đối tượng con hoặc đối tượng chứa sẽ tự động đủ điều kiện để thu gom rác.
  4. Nếu một đối tượng chỉ có các tham chiếu trực tiếp qua WeakHashMap, nó sẽ đủ điều kiện để thu gom rác.

Nếu một finaltrường của một đối tượng được sử dụng lặp đi lặp lại trong quá trình tính toán chậm và sau đó đối tượng sẽ không bao giờ được sử dụng, thì đối tượng đó sẽ được giữ cho đến khi mã nguồn cuối cùng yêu cầu trường hoặc JIT có thể sao chép trường vào biến tạm thời rồi từ bỏ đối tượng trước khi tính toán?
supercat

@supercat: trình tối ưu hóa có thể viết lại mã thành một biểu mẫu nơi đối tượng không được tạo ở vị trí đầu tiên; trong trường hợp đó, nó có thể được hoàn thiện ngay sau khi hàm tạo của nó kết thúc, trừ khi đồng bộ hóa buộc một thứ tự giữa việc sử dụng của đối tượng và bộ hoàn thiện.
Holger

@Holger: Trong trường hợp JIT có thể thấy mọi thứ sẽ xảy ra với một đối tượng giữa việc tạo và từ bỏ nó, tôi không thấy bất kỳ lý do nào để JIT sớm sa thải người hoàn thiện. Câu hỏi thực sự sẽ là những gì mã cần làm để đảm bảo rằng trình hoàn thiện không thể kích hoạt trong một phương thức cụ thể. Trong .NET có một GC.KeepAlive()hàm không làm gì ngoài việc buộc GC phải cho rằng nó có thể sử dụng một đối tượng, nhưng tôi biết không có hàm nào như vậy trong Java. Người ta có thể sử dụng volatilebiến cho mục đích đó, nhưng sử dụng một biến chỉ cho mục đích như vậy có vẻ lãng phí.
supercat

@supercat: JIT không bắn quyết toán, nó chỉ sắp xếp mã để không giữ tham chiếu, tuy nhiên, có thể có ích khi trực tiếp chinh phục FinalizerReference, do đó không cần chu trình GC để tìm ra rằng không có chu trình người giới thiệu. Đồng bộ hóa là đủ để đảm bảo mối quan hệ xảy ra trước khi ; vì việc hoàn thiện có thể (thực sự là) đang chạy trong một luồng khác, nên nó thường là chính thức cần thiết. Java 9 sẽ thêm Reference.reachabilityFence...
Holger

@Holger: Nếu JIT tối ưu hóa việc tạo đối tượng, việc duy nhất Finalizesẽ được gọi hoàn toàn sẽ là nếu mã JIT tạo ra mã trực tiếp. Mã đồng bộ hóa thường được mong đợi cho các đối tượng chỉ mong được sử dụng trong một luồng? Nếu một đối tượng thực hiện một số hành động cần được hoàn tác trước khi nó bị hủy (ví dụ: mở kết nối ổ cắm và có được quyền sử dụng riêng cho tài nguyên ở đầu bên kia), thì việc hoàn tất đóng kết nối trong khi mã vẫn đang sử dụng ổ cắm sẽ là một thảm họa . Sẽ là bình thường khi mã sử dụng đồng bộ hóa ...
supercat

2

phương thức hoàn thiện không được đảm bảo. Phương thức này được gọi khi đối tượng trở thành đủ điều kiện cho GC. Có nhiều tình huống mà các đối tượng có thể không được thu gom rác.


3
Sai. Những gì bạn đang nói là một đối tượng được hoàn thành khi nó không thể truy cập được. Nó thực sự được gọi khi phương thức thực sự được thu thập.
Stephen C

2

Đôi khi khi nó bị phá hủy, một đối tượng phải thực hiện một hành động. Ví dụ: nếu một đối tượng có tài nguyên không phải là java như tệp xử lý tệp hoặc phông chữ, bạn có thể xác minh rằng các tài nguyên này được giải phóng trước khi phá hủy một đối tượng. Để quản lý các tình huống như vậy, java cung cấp một cơ chế gọi là "hoàn thiện". Bằng cách hoàn thiện nó, bạn có thể xác định các hành động cụ thể xảy ra khi một đối tượng sắp bị xóa khỏi trình thu gom rác. Để thêm một bộ hoàn thiện vào một lớp, chỉ cần xác định quyết toán () phương thức . Thời gian thực hiện Java gọi phương thức này bất cứ khi nào nó sắp xóa một đối tượng của lớp đó. Trong phương thức hoàn thiện ()bạn chỉ định các hành động sẽ được thực hiện trước khi phá hủy một đối tượng. Trình thu gom rác được định kỳ tìm kiếm các đối tượng không còn tham chiếu đến bất kỳ trạng thái đang chạy hoặc gián tiếp bất kỳ đối tượng nào khác có tham chiếu. Trước khi một tài sản được giải phóng, bộ thực thi Java gọi phương thức Finalize () trên đối tượng. Phương thức Finalize () có dạng tổng quát sau:

protected void finalize(){
    // This is where the finalization code is entered
}

Với từ khóa được bảo vệ , việc truy cập để hoàn thiện () bằng mã bên ngoài lớp của nó bị ngăn chặn. Điều quan trọng là phải hiểu rằng quyết toán () được gọi ngay trước khi thu gom rác. Nó không được gọi khi một đối tượng rời khỏi phạm vi, ví dụ. Điều đó có nghĩa là bạn không thể biết khi nào, hoặc nếu, quyết toán () sẽ được thực thi. Kết quả là, chương trình phải cung cấp các phương tiện khác để giải phóng tài nguyên hệ thống hoặc các tài nguyên khác được sử dụng bởi đối tượng. Bạn không nên dựa vào hoàn thiện () để chạy chương trình bình thường.


1

Lớp nơi chúng ta ghi đè phương thức hoàn thiện

public class TestClass {    
    public TestClass() {
        System.out.println("constructor");
    }

    public void display() {
        System.out.println("display");
    }
    @Override
    public void finalize() {
        System.out.println("destructor");
    }
}

Cơ hội hoàn thiện phương thức được gọi

public class TestGarbageCollection {
    public static void main(String[] args) {
        while (true) {
            TestClass s = new TestClass();
            s.display();
            System.gc();
        }
    }
}

Khi bộ nhớ bị quá tải với các đối tượng kết xuất, gc sẽ gọi phương thức hoàn thiện

chạy và xem bàn điều khiển, nơi bạn không tìm thấy phương thức hoàn thiện được gọi thường xuyên, khi bộ nhớ bị quá tải thì phương thức hoàn thiện sẽ được gọi.


0

Java cho phép các đối tượng thực hiện một phương thức gọi là Finalize () có thể được gọi.

Phương thức Finalize () được gọi nếu trình thu gom rác cố gắng thu thập đối tượng.

Nếu trình thu gom rác không chạy, phương thức sẽ không được gọi.

Nếu trình thu gom rác thất bại trong việc thu thập đối tượng và cố gắng chạy lại nó, phương thức sẽ không được gọi trong lần thứ hai.

Trong thực tế, bạn rất khó sử dụng nó trong các dự án thực tế.

Chỉ cần nhớ rằng nó có thể không được gọi và chắc chắn nó sẽ không được gọi hai lần. Phương thức Finalize () có thể chạy 0 hoặc một lần.

Trong đoạn mã sau, phương thức Finalize () không tạo ra đầu ra khi chúng ta chạy nó vì chương trình thoát ra trước khi có nhu cầu chạy trình thu gom rác.

Nguồn


0

finalize()được gọi ngay trước khi thu gom rác. Nó không được gọi khi một đối tượng đi ra khỏi phạm vi. Điều này có nghĩa là bạn không thể biết khi nào hoặc ngay cả khifinalize() được thực thi.

Thí dụ:

Nếu chương trình của bạn kết thúc trước khi trình thu gom rác xảy ra, thì finalize()sẽ không thực thi. Do đó, nó nên được sử dụng làm thủ tục sao lưu để đảm bảo xử lý đúng các tài nguyên khác hoặc cho các ứng dụng sử dụng đặc biệt, không phải là phương tiện mà chương trình của bạn sử dụng trong hoạt động bình thường.


0

Như được chỉ ra trong https://wiki.sei.cmu.edu/confluence/display/java/MET12-J.+Do+not+use+finalulators ,

Không có thời gian cố định mà tại đó các trình hoàn thiện phải được thực thi vì thời gian thực hiện phụ thuộc vào Máy ảo Java (JVM). Bảo đảm duy nhất là bất kỳ phương thức hoàn thiện nào thực hiện sẽ làm như vậy sau khi đối tượng liên quan trở nên không thể truy cập được (được phát hiện trong chu kỳ thu gom rác đầu tiên) và đôi khi trước khi người thu gom rác lấy lại bộ lưu trữ của đối tượng liên quan (trong chu kỳ thứ hai của người thu gom rác) . Việc thực thi bộ hoàn thiện của một đối tượng có thể bị trì hoãn trong một thời gian dài tùy ý sau khi đối tượng không thể truy cập được. Do đó, việc gọi chức năng quan trọng về thời gian như đóng xử lý tệp trong phương thức quyết toán () của đối tượng là có vấn đề.


-3

Hãy thử chạy chương trình này để hiểu rõ hơn

public class FinalizeTest 
{       
    static {
        System.out.println(Runtime.getRuntime().freeMemory());
    }

    public void run() {
        System.out.println("run");
        System.out.println(Runtime.getRuntime().freeMemory());
    }

     protected void finalize() throws Throwable { 
         System.out.println("finalize");
         while(true)
             break;          
     }

     public static void main(String[] args) {
            for (int i = 0 ; i < 500000 ; i++ ) {
                    new FinalizeTest().run();
            }
     }
}
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.