1. 程式人生 > >02 JVM 從入門到實戰 | 什麽樣的對象需要被 GC

02 JVM 從入門到實戰 | 什麽樣的對象需要被 GC

情況下 標記 優點 引用計數器 基本 變量 pre 覆蓋 flash

引言

上一篇文章 JVM 基本介紹 我們了解了一些基本的 JVM 知識,本篇開始逐步學習垃圾回收,我們都知道既然叫垃圾回收,那回收的就應該是垃圾,可是我們怎麽知道哪些對象是垃圾呢? 哪些對象需要被回收? 什麽時候需要回收呢?

判斷算法

引用計數算法

給每個對象設置一個計數器,每當該對象被引用時引用計數器加 1,有引用斷開時引用計數減 1。當引用計數為 0 時表示該對象可以被回收。

這個可以用數據算法中的圖形表示,對象 A-對象 B-對象 C 都有引用,所以不會被回收,對象 B 由於沒有被引用,沒有路徑可以達到對象 B,對象 B 的引用計數就就是 0,對象 B 就會被回收。

技術分享圖片

優點

客觀地說,引用計數算法的實現簡單,判定效率也很高,在大部分情況下它都是一個不錯的算法,也有一些比較著名的應用案例,例如微軟公司的 COM(Component Object Model)技術、使用 ActionScript 3 的 FlashPlayer、Python 語言和在遊戲腳本領域被廣泛應用的 Squirrel 中都使用了引用計數算法進行內存管理。

缺點

主流的 Java 虛擬機裏面沒有選用引用計數算法來管理內存,其中最主要的原因是它很難解決對象之間相互循環引用的問題。因為相互引用,計數器值永遠也不會成為 0 ,所以永遠達不到被 GC 回收的條件。

例如下圖:對象A,對象B 循環引用,沒有其他的對象引用A和B,則A和B 都不會被回收。

技術分享圖片

可達性分析算法

該算法的原理是:以 GC Roots 的對象作為起始點,然後以該節點為基準開始向下搜索,搜索過程中搜索路徑我們稱之為引用鏈,當一個對象到 GC Roots 沒有任何引用鏈連接的時候,說明該對象是不可用的。

註意:我們在 上邊的所說的引用都是指定的強引用關系。

因為我們知道 Java 中存在四種引用對象,根據引用強度(從上至下依次減弱)可依次劃分為:

  • 強引用 Strong Reference
  • 軟引用 Weak Reference
  • 弱引用 Phantom Reference
  • 虛引用 Soft Reference

詳細的概念大家下去可以自行查看,此處不再贅述。

可以用作 GC Roots 的對象
  • 方法區 : 類靜態變量引用的對象
  • 方法區 : 常量引用的對象
  • 虛擬機棧 : 本地變量表中引用的對象
  • 本地方法棧 : JNI (帶 Native 關鍵字)引用的對象

如下圖,對象 D 和根對象之間毫無引用鏈,則會被回收。

技術分享圖片

由於這種算法即使存在互相引用的對象,但如果這兩個對象無法訪問到根對象,還是會被回收。如下圖:對象 C 和對象 D 互相引用,但是由於無法訪問根節點,還是會被回收。

技術分享圖片

不可達是不是就一定會被回收?

答案是不一定。

一個對象在真正被回收之前,需要經歷兩次標記過程:

第一次標記:

如果對象在進行可達性分析之後發現沒有與 GC Roots 相連接的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize() 方法,此時分兩種情況:

  • 對象沒有覆蓋 finalize() 方法

  • finalize() 方法已經被虛擬機調用過

在這兩種情況下虛擬機都認為此時沒有必要執行垃圾回收。

第二次標記:

在第一次標記判定基礎之上,如果判定為有必要執行 finalize() 方法,則虛擬機會把這個對象放置到一個叫做 F-Queue 的隊列之中,並在之後用 Finalizer 線程去執行回收。(此處的執行指的是 虛擬機會去觸發這個方法,但是並不保證回收成功,也不承諾會等待他運行結束,因為如果有個別對象在 finalize() 方法中執行緩慢甚至發生死循環的時候,有可能會導致 F-Queue 隊列中的其他對象發生永久等待,最後導致整個垃圾回收系統崩潰)

總結

  • 引用計數法:簡單高效,但是對於相互循環引用的對象無法判斷是否應該被回收
  • 可達性分析:目前大多虛擬機廠商采用的垃圾回收算法,通過判斷其他對象是否和根節點之間存在引用鏈來分析是否應該被回收
  • 不可達的對象不一定會立即被回收

02 JVM 從入門到實戰 | 什麽樣的對象需要被 GC