【JVM虛擬機】深入理解JVM垃圾回收算法

瞭解虛擬機就一定需要了解垃圾回收機制,人們很久就在思考這三件事:那些內存需要回收?什麼時候回收?怎樣回收?在這篇文章是自己參考《深入理解Java虛擬機》一書學習後的總結,看完相信就會對垃圾回收有了一定的瞭解。

一、哪些內存需要回收?

之前講過的JVM內存的各個區域,我們知道程序計數器、虛擬機棧、本地方法棧 三個區域是線程私有的,也就是隨線程生而生,隨線程滅而滅。因爲這些區域的內存分配和回收都具備確定性,在這幾個區域內就不需要過多的考慮回收問題。而Java堆和方法區則不一樣,我們只有在程序處於運行期間是才能知道會創建哪些對象,這部分內存的分配和回收都是動態的,這也是垃圾回收器工作的主要區域。

1.對象是否還有用?

(1) 引用計數法

有一種方法就是使用引用計數法,它是給對象中添加一個引用計數器,當有一個地方引用它時,計數器的數值就加1,反之當引用失效時,計數器的值就減1。那麼任何時刻計數器爲0的對象就是不可能再被使用的。雖然引用計數法的實現簡單判定效率高,大部分情況下都是一個不錯的選擇,但Java虛擬機中卻不是使用該種方法來管理內存最主要的原因就是它很難解決對象之間互相循環引用的問題.即如果兩個對象互相引用即使以後都不在需要,那麼垃圾回收器也不會回收.

(2) 可達性分析算法

在主流的語言中,都是通過可達性分析來判斷對象是否"存活"。這個算法的思路就是通過一系列的"GC Roots"的對象作爲起始點,從這些節點向下搜尋,搜索所走過的路徑稱爲"引用鏈",當一個對象到GC Roots不可達時,就證明該對象是不可用的.
在Java語言中,可以作爲GC Roots 的對象包括下面幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中引用的對象

2.引用的四種分類

在JDK 1.2之後,Java對引用的概念進行了擴充,將引用分爲強引用、軟引用、弱引用、虛引用,這四種引用強度一次逐漸減弱。

  • 強引用:指的是程序代碼中普遍存在的,類似Object obj = new Object()這類引用只要強引用存在,垃圾回收器就永遠不會回收被引用的對象。
  • 軟引用:用來描述一些還有用但並非必須的對象。對於軟引用關聯的對象,若內存足夠,就不去回收,如果內存即將不夠,就會對這些引用進行回收.
  • 弱引用:是用來描述非必需對象的,被弱引用關聯的對象只能生存帶下一次垃圾回收發生之前,當垃圾收集器工作時,無論內存是否足夠都會進行回收.
  • 虛引用:也稱爲幽靈引用或者幻影引用,是最弱的引用關係,一個對象是否存在弱引用不會對其生存時間構成影響,也無法通過虛引用來取得一個對象的實例。所以虛引用唯一的作用就是在一個對象被回收時收到一個系統通知.

3、finalize()方法

即使在可達性分析算法中不可達的對象,也不是必定會被回收。對於每一個對象都要進行兩次標記,當在可達性分析爲不可達時就第一次被標記會回收,此時被標記的對象還有一次可以“拯救”自己的機會,那就是對象是否會執行finalize()方法。當對象沒有覆蓋該方法或者該方法已經被調用過一次,那麼就進行了第二次標記,也即基本上一定會被回收。
當對象覆蓋了finalize()方法,那麼這個對象就會被放置在一個F-Queue隊列中,並稍後由虛擬機自動建立的一個低優先級的線程區執行。如果在執行對象的finalize()方法時,對象在該方法中成功“拯救自己”——重新與引用鏈上的一個對象建立關聯,那麼這個對象就會重新變爲可用對象。如果對象沒有成功“拯救自己”那麼也會被標記第二次而被回收。注意:任何一個對象的finalize()方法智慧被調用一次,如果面臨第二次回收,就不會再次執行finalize()方法。

所以我們最後得到,如果一個堆上的對象在可達分析算法中到GC Roots中沒有沒有任何引用鏈相連,以及兩次被標記回收,那麼這個對象就會被垃圾回收器回收。

3、回收方法區

上面我們得到堆上的對象的回收規則,那麼對於方法區一般會認爲是沒有垃圾回收的,因爲比起堆上的垃圾回收相率極低。方法區中的垃圾回收器主要回收兩部分:廢棄的常量和無用的類。廢棄常量指的是一個常量在常量池中但卻沒有引用指向,這個常量就會被清理出常量池。判斷常量無用比較簡單,但對於一個類是否是一個無用的類的判斷卻比較苛刻:

  1. 該類的所有實例都已經被回收,也即Java堆中不存在該類的任何實例
  2. 加載類的ClassLoader已經被回收
  3. 該類對應的java.lang.Class對象沒有在任何地方被引用

滿足上述三個條件的類虛擬機可以進行回收,但並不一定會被回收,可以通過參數設置來調整。

二、垃圾回收算法

這裏介紹四種垃圾回收的算法的思想:

1.標記—清除算法(Mark-Sweep)

最基礎的收集算法就是“標記—清除”算法,這個算法分爲兩個階段:標記、清除。首先標記出所有需要回收的對象,在比偶埃及完成後統一回收所有被標記的對象。
這種方法的不足有兩個:

  1. 效率問題:標記和清除兩個效率都不高
  2. 空間問題:標記—清除後會產生大量的不連續的內存碎片,空間碎片太多導致以後在運行過程中需要分配法對象時,無法找到連續的內存而不得不再次觸發垃圾回收。

在這裏插入圖片描述

2.複製算法(Copying)

爲了解決第一中方法的效率問題,複製算法就出現了,它將可用的內存劃分爲內存相等的兩塊區域,每次只使用其中一塊,當一塊內存用完後,就將存活着的對象都複製到另一塊內存中,然後把已經使用的空間一次性清理掉。這樣分配內存空間就不會有內存碎片的情況,只需移動堆頂指針按順序分配空間。這種方式實現簡單、運行高效。但這種方式的不足就是將內存空間縮小爲原來的一半,對空間的損耗較大。
在這裏插入圖片描述
現在的商業虛擬機都通過這種方法來回收“新生代”對於新生代的對象98%都是“朝生夕死”,所以不需要按照1:1的比例來劃分內存空間而是將內存空間分爲一個大的Eden空間和兩塊小的Survivor空間,每次都使用一塊大空間和一塊小空間,當回收時將存活的對象複製到另一個小空間中,最後清理掉用過的空間。其中Eden空間和Survivor空間大小比例是8:1,也就相當於每次只有10%的空間“浪費”。當然如果存活對象較多,多於10%時,需要依賴其他內存進行分配擔保。

3.標記—整理算法(Mark—Compact)

複製算法在對象存活率較高時就要進行很多次的複製操作,降低了效率,而且也會浪費50%的空間,所以老年代一般不會採取這種方法。根據老年代的特點,就有了標記—整理算法,標記過程與標記—清除算法中的標記相同,但後續步驟不是直接堆可回收的對象進行清理,而是讓所有的存活對象都向一端移動,然後直接清除端邊界以外的內存。
在這裏插入圖片描述

4、分代收集算法(Generational Collection)

當前多數的虛擬機 都是採取的分代收集算法,這個算法時根據對象的存活週期不同將內存劃分爲幾塊,一般將Java堆分成新生代和老年代,這樣就可以更具各個年代的特點選擇最適當的收集算法。在新生代中,每次垃圾回收時都有大批對象被回收,少量存活於是就使用“複製算法”。而老年代因爲對象的存活率較高就需要使用“標記—清理”或者“標記—整理”的算法來進行回收。

新生代用 “複製算法”,老年代用 “標記—清理”或者“標記—整理”算法

三、什麼時間發生GC呢?

1.安全點(SafePoint)

是不是任何時刻都能發生GC呢?當然不是。程序並不能在所有地方都可以停頓下來GC,只有在達到安全點纔會執行。要知道如果進行GC操作首先要讓整體程序在這一時刻保證不變,像是一個被凍結到某一點的狀態,引用關係也不會再發生改變。此時虛擬機通過自己的方式會獲取到在這些特定的點上的所有GC Roots的引用,只有在這種情況下也即“安全點”纔可以發生GC。
選取安全點時既不可以太少以至於讓GC等太久,也不可以太多以至於增大運行時負荷。所以安全點的選定基本上是以程序“是否具有讓程序長時間等待的特性”而選擇的,例如在方法調用、循環跳轉、異常跳轉等纔會產生安全點。
另一個問題就是如何在GC發生時讓所有的線程都跑到最近的“安全點”上停下來呢?這裏介紹兩種方案:

  1. 搶先式中斷(Preemptive Suspension):首先把所有線程都中斷,如果發現線程中斷的地方不在安全點上,就恢復線程讓其運行到安全點上,這種方式現在已經很少用了。
  2. 主動式中斷(Voluntary Suspension):不需要GC對線程操作,只需要設置一個標誌位。各個線程運行時會遇到安全點就會主動去查看這個標誌位,發現中斷標誌爲真就自己中斷掛起。

2.安全區域(Safe Region)

上述安全點似乎確實解決了問題,但如果是線程“不執行”的時候呢?典型的就是Sleep和Blocked狀態下的線程,就無法相應JVM的中斷請求,去安全點的位置掛起,對於這種情況就需要安全區域來解決。
安全區域是指:在一段時間下或一段代碼片段中,引用關係不會發生變化,在這個區域任何時刻開始GC都是安全的,安全區域可以看作是安全點的擴展。
在線程執行到安全區域的代碼塊是,首先就會標識自己進入了安全區域,這樣在這段時間要發起GC時,就不用管已經在安全區域的線程了。在線程要離開安全區域時,要檢查系統是否完成了整個GC過程,如果沒有,那線程就會繼續等待,如果完成那麼就可以離開安全區域。

GC將會發生在安全點和安全區域時

內存回收的具體進行過程時由虛擬機所採用的GC收集器決定的,而一臺虛擬機也往往不只有一種GC收集器。

以上就是關於JVM的引用的一些特性以及JVM的垃圾回收機制的學習總結,也解答了開始時的三個問題。有任何問題歡迎指正,希望能幫助到你,也歡迎點贊關注一起進步。

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