1. 程式人生 > >Java虛擬機器核心知識(四) 如何判斷物件是否需要回收

Java虛擬機器核心知識(四) 如何判斷物件是否需要回收

前言

前面兩篇文章,給大家講解了Java的記憶體區域以及記憶體溢位和記憶體洩漏的區別,對於程式計數器虛擬機器棧本地方法棧這三個區域的資料,它們的生命週期可以說是伴隨著整個執行緒週期,每個棧幀分配多少記憶體,也基本是在類結構確定的時候就已知了(即編譯期),因此這幾個區域的記憶體分配和回收,都具有確定性,我們不需要考慮太多的回收問題。

我們常說的垃圾回收,主要指的是Java堆方法區的垃圾回收。那為什麼我們關注的垃圾回收在這一部分呢?

其實是由於編譯期只知道物件的靜態型別,一個方法中需要建立多少物件,也只有在執行期才知道,因此,這些部分的記憶體分配和回收都是動態的,垃圾收集器關注的是這部分的記憶體。

物件回收的判斷策略

引用計數演算法

這種演算法,給每個物件設定一個引用計數器,每當有一個地方引用它時,計數器加1;引用失效時,計數器減1;計數器為0,意味著物件獨自在堆中,上,不可能再被使用,這時就可以回收了。 在這裡插入圖片描述 這種演算法,實現簡單,效率也很高,但是有一個致命的缺陷——很難解決物件之間相互引用的問題。

可達性分析演算法

這個演算法的基本思路是:通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,當GC Roots到一個物件不可達時,則證明這個物件是不可用的,可以將其回收。 在這裡插入圖片描述 在Java中,可作為GC Roots的物件主要有兩種:

(1) 全域性性的物件,如常量或者類的靜態屬性,如果一個物件被全域性物件所引用,那就不能被回收。

(2) 執行上下文,如棧幀中的區域性變數,如果方法上下文中有區域性變數引用了這個物件,那就不能被回收。

總體來說,這些物件可以是:

虛擬機器棧(棧幀中的本地變量表)中引用的物件。

方法區中的類靜態屬性引用的物件

方法區中的常量引用的物件

本地方法棧JNI中的引用的物件。

可達就一定不會被回收?

預設情況下,到GC Roots可達的物件都不會被回收,這種物件,我們成為“強引用”。

Java對引用的概念進行了擴充,將引用分為強引用(StrongReference)、軟引用(SoftReference)、弱引用(WeakReference)和虛引用(PhantomReference)4種:

強引用

:也就是預設的引用,只要到GC Roots可達,就不會被回收;

軟引用:物件在將要發生記憶體溢位之前,會被回收;

弱引用:物件在下一次GC時被回收;

虛引用:形同虛設,虛引用的物件,可以視為GC Roots不可達的物件。

不可達就一定會回收?

根搜尋演算法中不可達的物件也並非是‘非死不可’的,暫時是‘緩刑’階段,要真正判斷一個物件死亡要經歷兩次標記過程:如果物件在進行根搜尋後發現物件不可達,那它將會進行被第一次標記並且進行篩選,篩選的條件是此物件是否有必要執行finalize()方法。當物件沒有覆蓋finalize()方法或者finalize()方法已經被虛擬機器掉用過,這兩種情況都視為‘沒有必要執行’。

如果物件被認為有必要執行finalize()方法,那麼這個方法會被放置在一個名為F-Queue的佇列之中,並在稍後由一條由虛擬機器自動建立的、低優先順序的Finalizer執行緒去執行。這裡的‘執行’也只是指虛擬機器會觸發這個方法,但並不承諾一定會執行。

finalize()方法是物件逃脫死亡命運的最後一次機會,稍後GC會對F-Queue中的物件進行第二次小規模的標記,如果物件在finalize()中重新與引用鏈上的任何一個物件建立了關聯,就會被移出‘即將回收’集合,如果沒有移出,那就真的離死亡不遠了。

finalize()方法只會被系統自動呼叫一次。

參考資料

《深入理解Java虛擬機器》 周志明