簡體   English   中英

為什么finalize()僅在創建新對象之后執行,而不在調用gc()之后執行?

[英]Why does finalize() execute only after new object is created, but not after gc() is invoked?

調用gc()時不應該立即執行finalize()嗎? 輸出結果的順序有點令人信服。

class Test
{
    int x = 100; 
    int y = 115;

    protected void finalize()
    {System.out.println("Resource Deallocation is completed");}
}

class DelObj
{
    public static void main(String arg[])
    {
        Test t1 = new Test();           
        System.out.println("Values are "+t1.x+", "+t1.y+"\nObject refered by t1 is at location: "+t1);
        t1 = null; // dereferencing
        System.gc(); // explicitly calling

        Test t2= new Test();
        System.out.println("Values are "+t2.x+", "+t2.y+"\nObject refered by t2 is at location: "+t2);

    } 
}

創建一個新對象(由t2引用)后得到finalize()的執行結果:

D:\JavaEx>java DelObj
Values are 100, 115
Object refered by t1 is at location: Test@6bbc4459
Values are 100, 115
Object refered by t2 is at location: Test@2a9931f5
Resource Deallocation is completed

調用System.gc()僅向JVM提供提示,但不能保證會發生實際的垃圾回收。

但是,您期望的更大問題是垃圾回收與最終化不同。

參考Java 6文檔, System.gc()聲明:

運行垃圾收集器。

調用gc方法表明,Java虛擬機將花費更多精力來回收未使用的對象,以使它們當前占用的內存可用於快速重用。 ...

System.runFinalization()比較:

運行任何未完成的對象的終結方法。

調用該方法表明,Java虛擬機將花更多的精力來運行已發現被丟棄但尚未運行其finalize方法的對象的finalize方法。 ...

因此,可能會有“待定”。 “已被發現丟棄但尚未運行其終結方法的對象”。

不幸的是,Java 6的finalize()文檔以具有誤導性的句子開頭:

當垃圾回收確定不再有對該對象的引用時,由垃圾回收器在對象上調用。

而垃圾回收和終結處理是兩回事,因此,垃圾回收器不會調用finalize()方法。 但是請注意,后續文檔中指出:

Java編程語言不保證哪個線程將為任何給定對象調用finalize方法。

所以,當你說“輸出結果的順序是有點難以服眾”,還記得我們在談論線程多在這里,所以在沒有額外的同步的,順序你的控制范圍之外。

Java語言規范甚至說:

Java編程語言沒有指定終結器將被調用的時間,只是說它將在重新使用對象的存儲之前發生。

后來

Java編程語言不對finalize方法調用施加任何順序。 終結器可以按任何順序調用,甚至可以同時調用。

實際上,垃圾收集器只會使需要終結的對象排隊,而一個或多個終結器線程將輪詢隊列並執行finalize()方法。 當所有終結器線程都忙於執行特定的finalize()方法時,需要終結的對象隊列可能會任意長。

請注意,現代JVM對那些沒有專用finalize()方法的類進行了優化,即從Object繼承該方法或僅​​具有一個空方法。 這些類的實例(所有對象中的大多數)將跳過此完成步驟,並立即回收其空間。

因此,如果您添加了finalize()方法只是為了發現對象何時被垃圾回收,則正是該finalize()方法的存在降低了內存回收的速度。

因此,最好參考JDK 11版本的finalize()

已過時。

最終確定機制本質上存在問題。 終結處理可能會導致性能問題,死鎖和掛起。 終結器中的錯誤可能導致資源泄漏; 如果不再需要取消終結,則無法取消; 並且在用於終結不同對象的方法的調用之間未指定任何順序。 此外,無法保證最終確定的時間。 只有在不確定的延遲之后(如果有的話),才可以在可終結對象上調用finalize方法。 其實例擁有非堆資源的類應提供一種方法來啟用這些資源的顯式釋放,並且在適當時,它們還應實現AutoCloseable。 當對象變得不可訪問時,Cleaner和PhantomReference提供了更靈活和有效的方式來釋放資源。

因此,當您的對象不包含非內存資源時,因此實際上不需要終結處理,可以使用

class Test
{
    int x = 100;
    int y = 115;
}

class DelObj
{
    public static void main(String[] arg)
    {
        Test t1 = new Test();
        System.out.println("Values are "+t1.x+", "+t1.y+"\nObject refered by t1 is at location: "+t1);
        WeakReference<Test> ref = new WeakReference<Test>(t1);
        t1 = null; // dereferencing
        System.gc(); // explicitly calling
        if(ref.get() == null) System.out.println("Object deallocation is completed");
        else System.out.println("Not collected");

        Test t2= new Test();
        System.out.println("Values are "+t2.x+", "+t2.y+"\nObject refered by t2 is at location: "+t2);

    }
}

System.gc()調用仍然只是一個提示,但是在大多數實際情況下,您將發現隨后收集對象。 請注意,為對象打印的哈希碼(例如Test@67f1fba0與存儲位置無關。 那是一個頑強的神話。 對象內存地址后面的模式通常不適合哈希 ,此外,大多數現代JVM可以在其生命周期內將對象移動到不同的內存位置,而身份哈希代碼則保證保持不變。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM