1. 程式人生 > 其它 >無法訪問已釋放的物件。_31、垃圾回收演算法 — 物件的finalization機制

無法訪問已釋放的物件。_31、垃圾回收演算法 — 物件的finalization機制

技術標籤:無法訪問已釋放的物件。

01

物件的finalization機制

  1. Java 語言提供了物件終止(finalization)機制來允許開發人員提供物件被銷燬之前的自定義處理邏輯。

  2. 當垃圾回收期發現沒有引用指向一個物件時,即:回收此物件之前,總會先呼叫這個物件的 finalize()方法

  3. finalize()方法允許在子類中被重寫,用於在物件被回收時進行資源釋放。通常在這個方法中進行一些資源釋放和清理工作,比如關閉檔案、套接字和資料庫連線等。

  4. 永遠不要主動呼叫某個物件的 finalize()方法,應該交由垃圾回收機制呼叫,因為:

  • 在 finalize()時可能會導致物件復活

  • finalize()方法的執行時間是沒有保障的,它完全由GC執行緒決定,並且優先順序很低,極端情況下,如果不發生GC,則finalize()將沒有執行機會

  • 一個糟糕的finalize()會嚴重影響GC效能

如果從所有根節點都無法訪問到某個物件,說明物件已經不再使用。一般來說,此物件需要被回收。但是這個物件也不一定“非死不可”,它可能會復活自己。由於 finalize()方法的存在,虛擬機器中的物件一般處於三種可能狀態:

  • 可觸及的:從根節點開始,可以到達這個物件

  • 可復活的:物件所有的引用都被釋放,但是物件在 finalize()中復活

  • 不可觸及的:物件的 finalize()被呼叫,並且沒有復活,就會進入此狀態。此物件一定會被回收。finalize()在垃圾回收之前只會呼叫一次。

判斷一個物件objA是否可以回收,至少要經歷兩次標記。

02

finalization機制具體過程

  1. 如果物件objA到GCRoots沒有引用鏈,就進行第一次標記

  2. 進行篩選,判斷此物件是否有必要執行finalize()方法

  • 如果objA沒有重寫finalize() 方法,或者finalize() 已經被虛擬機器呼叫過,則虛擬機器視為“沒必要執行”,objA被判定為“不可觸及”狀態

  • 如果物件objA重寫了finalize()方法,且還未執行過,那麼objA會被插入到F-Queue佇列中,由一個虛擬機器自動建立的、低優先順序的Finalizer執行緒觸發 finalize()方法執行

  • finalize() 方法是物件逃脫死亡的最後機會,稍後 GC 會對 F-Queue 佇列中的物件進行第二次標記。如果objA在 finalize()方法中與引用鏈上的任何一個物件建立了聯絡,那麼在第二次標記時,objA就會被移出“即將回收”的集合。之後,物件會再次出現沒有引用存在的情況。在這個情況下,finalize 方法不會被再次呼叫,物件會直接變成不可觸及狀態,也就是說,一個物件的 finalize() 方法自始至終只會被呼叫一次。

03

程式碼演示可復活的物件

  1. 不重寫 finalize()方法:

public class GCTest {    public static GCTest obj; // 類變數,屬於 GC Roots    public static void main(String[] args){        try {            obj = new GCTest();            // 物件第一次拯救自己            obj = null;            System.gc(); // 呼叫垃圾回收器            System.out.println("第一次 GC");            // 由於 Finalizer 執行緒優先順序很低,暫停2秒,等待Finalizer執行            Thread.sleep(2000);            if (obj == null){                System.out.println("obj 物件已死");            }else{                System.out.println("obj 物件仍然存活");            }            System.out.println("第二次 GC");            obj = null;            System.gc(); // 呼叫垃圾回收器            // 由於 Finalizer 執行緒優先順序很低,暫停2秒,等待Finalizer執行            Thread.sleep(2000);            if (obj == null){                System.out.println("obj 物件已死");            }else{                System.out.println("obj 物件仍然存活");            }        }catch (Exception e){            e.printStackTrace();        }    }}控制檯列印:第一次 GCobj 物件已死第二次 GCobj 物件已死

重寫 finalize()方法:

public class GCTest {    public static GCTest obj; // 類變數,屬於 GC Roots    @Override    protected void finalize() throws Throwable {        super.finalize();        System.out.println("呼叫當前類重寫的 finalize() 方法");        obj = this; // obj 在呼叫 finalize() 時復活了自己    }    public static void main(String[] args){        try {            obj = new GCTest();            // 物件第一次拯救自己            obj = null;            System.gc(); // 呼叫垃圾回收器            System.out.println("第一次 GC");            // 由於 Finalizer 執行緒優先順序很低,暫停2秒,等待Finalizer執行            Thread.sleep(2000);            if (obj == null){                System.out.println("obj 物件已死");            }else{                System.out.println("obj 物件仍然存活");            }            System.out.println("第二次 GC");            obj = null;            System.gc(); // 呼叫垃圾回收器            // 由於 Finalizer 執行緒優先順序很低,暫停2秒,等待Finalizer執行            Thread.sleep(2000);            if (obj == null){                System.out.println("obj 物件已死");            }else{                System.out.println("obj 物件仍然存活");            }        }catch (Exception e){            e.printStackTrace();        }    }}控制檯列印:第一次 GC呼叫當前類重寫的 finalize() 方法obj 物件仍然存活第二次 GCobj 物件已死
bc74d730aceffc80909f40753c129e45.png

掃碼關注我

微訊號|fancheng1995

dfebbc1af60937c3fcc1179fc7a88837.gif

你們點點“分享”,給我充點兒電吧~