深入理解Java虛擬機(四):關於對象

引言

在堆裏面存放着Java世界中幾乎所有的對象實例,垃圾收集器在對堆進行回收前,第一件事情就是要確定這些對象之中哪些還“存活”着,哪些已經“死去”(“死去”即不可能再被任何途徑使用的對象)了。

判斷方法

1. 引用計數算法?

很多教科書判斷對象是否存活的算法是這樣的:在對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加一;當引用失效時,計數器值就減一;任何時刻計數器爲零的對象就是不可能再被使用的。

但是,在Java領域,至少主流的Java虛擬機裏面都沒有選用引用計數算法來管理內存,主要原因是,這個看似簡單的算法有很多例外情況要考慮,必須要配合大量額外處理才能保證正確地工作,譬如單純的引用計數就很難解決對象之間相互循環引用的問題。

2. 可達性分析算法

當前主流的商用程序語言(Java、C#,上溯至前面提到的古老的Lisp)的內存管理子系統,都是通過可達性分析(Reachability Analysis)算法來判定對象是否存活的。

可達性算法基本思路

通過一系列稱爲“GC Roots”的根對象作爲起始節點集,從這些節點開始,根據引用關係向下搜索,搜索過程所走過的路徑稱爲“引用鏈”(Reference Chain),如果某個對象到GCRoots間沒有任何引用鏈相連,或者用圖論的話來說就是從GC Roots到這個對象不可達時,則證明此對象是不可能再被使用的。

在這裏插入圖片描述
上圖中,對象object 5、object 6、object 7雖然互有關聯,但是它們到GC Roots是不可達的,因此它們將會被判定爲可回收的對象。

GC Roots的對象

在Java技術體系裏面,固定可作爲GC Roots的對象包括以下幾種:

  • 在虛擬機棧(棧幀中的本地變量表)中引用的對象,譬如各個線程被調用的方法堆棧中使用到的參數、局部變量、臨時變量等。
  • 在方法區中類靜態屬性引用的對象,譬如Java類的引用類型靜態變量。
  • 在方法區中常量引用的對象,譬如字符串常量池(String Table)裏的引用。
  • 在本地方法棧中JNI(即通常所說的Native方法)引用的對象。
  • Java虛擬機內部的引用,如基本數據類型對應的Class對象,一些常駐的異常對象(比如NullPointExcepiton、OutOfMemoryError)等,還有系統類加載器。
  • 所有被同步鎖(synchronized關鍵字)持有的對象。
  • 反映Java虛擬機內部情況的JMXBean、JVMTI中註冊的回調、本地代碼緩存等。

除了上述固定的GC Roots集合以外,根據用戶所選用的垃圾收集器以及當前回收的內存區域不同,還可以有其他對象“臨時性”地加入,共同構成完整GC Roots集合。

再談引用

判定對象是否存活都和“引用”離不開關係。

在JDK 1.2版之後,Java對引用的概念進行了擴充,將引用分爲強引用(Strongly Re-ference)軟引用(Soft Reference)弱引用(Weak Reference)虛引用(PhantomReference)4種。

這4種引用強度依次逐漸減弱。

強引用

指在程序代碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關係。無論任何情況下,只要強引用關係還存在,垃圾收集器就永遠不會回收掉被引用的對象。

軟引用

用來描述一些還有用,但非必須的對象。只被軟引用關聯着的對象,在系統將要發生內存溢出異常前,會把這些對象列進回收範圍之中進行第二次回收,如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。

弱引用

用來描述那些非必須對象,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生爲止。當垃圾收集器開始工作,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。

虛引用

也稱爲“幽靈引用”或者“幻影引用”,它是最弱的一種引用關係。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的唯一目的只是爲了能在這個對象被收集器回收時收到一個系統通知。

生存還是死亡?

即使在可達性分析算法中判定爲不可達的對象,也不是“非死不可”的,這時候它們暫時還處於“緩刑”階段。

要真正宣告一個對象死亡,至少要經歷兩次標記過程:

(1)如果對象在進行可達性分析後發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記。

(2)隨後進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。

假如對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,那麼虛擬機將這兩種情況都視爲“沒有必要執行”。

如果這個對象被判定爲確有必要執行finalize()方法,那麼該對象將會被放置在一個名爲F-Queue的隊列之中,並在稍後由一條由虛擬機自動建立的、低調度優先級的Finalizer線程去執行它們的finalize()方法。

如果對象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個對象建立關聯即可,譬如把自己(this關鍵字)賦值給某個類變量或者對象的成員變量,那在第二次標記時它將被移出“即將回收”的集合;如果對象這時候還沒有逃脫,那基本上它就真的要被回收了。

總結

如何判斷一個對象是否需要被回收?對象的引用有哪幾種?腦海中不禁產生了這些問題,這都是面試常問的一些問題,通過這樣的系列學習過程,相信我們都很清楚答案了。

下一篇,繼續學習,垃圾收集算法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章