1. 程式人生 > >深入理解jvm虛擬機讀書筆記-垃圾收集器與內存分配策略(二)

深入理解jvm虛擬機讀書筆記-垃圾收集器與內存分配策略(二)

具體實現 地方 比例 並發 解決 垃圾收集 替換 map 而是

垃圾收集算法-標記清除算法

標記清除算法是最基礎的收集算法。算法分為“標記”和“清楚”兩個階段:首先標記出所有需要回收的對象,在標記過程完成後統一回收所有被標記的對象。後續的收集算法都是基於這種思路對其不足進行改善。

主要有兩個不足點:

  • 一個是效率,標記和清除兩個過程的效率都不高;
  • 另一個是空間問題,標記清除後會產生大量不連續的內存碎片,碎片太多會導致以後在程序運行過程中需要分配較大的對象時,無法找到連續內存而不得不觸發另一次垃圾收集行為。

垃圾收集執行過程:

技術分享圖片

垃圾收集算法-復制算法

為了解決效率問題有了復制算法,它將可用的內存分為大小相等的兩塊,每次只使用其中一塊。當這一塊用完了,就將還存活的的對象復制到另一塊上面,然後把已使用過的內存空間一次性清理掉。不用考慮內存碎片等復雜情況。

技術分享圖片

現代商業虛擬機都用這種算法來回收新生代。因為新生代中98%的對象都是“朝生夕死”,所以不需要按照1:1來分配內存,而是將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用一塊Eden和一塊Survivor。Eden和Survivor的大小比例是8:1,只有10%的內存會被”浪費“掉,當回收之後有大於10%的對象存活,Survivor空間不夠用時,會通過內存擔保機制進入老年代。

缺點:

在存活率較高的時候需要進行較多的復制操作,效率將會變低。不適合用在老年代

tip:Java的堆內存分為老年代和新生代(後面會有詳細介紹)。

垃圾收集算法-標記整理算法

標記整理算法主要是用在存活率較高的老年代,標記過程與“標記清除算法”一樣,但是後續是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。如圖:

技術分享圖片

分代收集算法

分代收集不是一種新算法,只是根據對象存活周期不同將內存劃分為幾塊。一般是新生代和老年代。這樣就可以根據每個年代的特點采用最適當的收集算法。新生代中,對象存活率不高,可以選復制算法。老年代存活率高,可以采用“標記清理”或者“標記整理”算法。

HotSpot算法實現-枚舉根節點

枚舉根節點是用來做可達性分析的,判斷哪些對象還活著。因為這項分析工作必須要在一個能確保一致性的快照中進行,不可以出現分析過程中對象引用關系還在不管變化,這點導致GC進行時必須停頓所有Java執行線程(Sun稱這件事情為“Stop The World”)。

目前主流Java虛擬機都是準確式GC,所以當系統停頓下來後,並不需要一個不漏的檢查所有執行上下文和全局的引用位置,虛擬機使用一組稱為OopMap的數據結構來存放這些對象引用的位置。

安全點(Safepoint)

有了OopMap可以快速而準確的完成GC Roots枚舉,OopMap內容變化的指令非常多,如果為每一條指令都聲稱對應的OopMap,那將會需要大量的額外空間。

所以虛擬機只在“安全點”記錄這些信息,只有到達安全點才能暫停下來開始GC。安全點不能太少會讓GC等待時間太長,也不能太多會增大運行時負荷。所以安全點的選定是以“是否具有讓程序長時間執行的特征”來選定。比如:方法調用、循環跳轉等,具有這些指令才會產生安全點。

另一個問題時如何在GC時讓所有的線程“跑”到最近的安全點上停頓,有兩種方案:搶先式中斷和主動式中斷。

搶先式中斷:GC發生時,先中斷所有線程,如果線程不在安全點,再恢復線程,讓它跑到“安全點”,幾乎沒有虛擬機這麽做。

主動式中斷:當GC需要中斷線程時,僅僅設置一個標誌,各個線程執行時去輪詢這個標誌,發現中斷標誌為真時就中斷掛起,輪詢的標誌地方和安全點是重合的。

安全區域

Safepoint機制保證了程序執行時,在不太長的時間就會遇到可進入GC的Safepoint。如果程序“不執行”的時候比如處於Sleep和blocked狀態,程序就沒有分配cpu執行時間,這時候線程就無法響應JVM的中斷請求。這時候就需要安全區域(Safe Region)來解決。

安全區域是指在一段代碼中,引用關系不會發生變化。這個區域的任意地方開始GC都是安全的。

線程執行到安全區域的代碼時,首先標示自己已經進入了安全區域,當jvm發起GC時就不用管安全區域的線程了。

垃圾收集器

收集算法是內存回收的方法論,垃圾收集器是內存回收的具體實現。基於JDK1.7的所有垃圾收集器如圖:

技術分享圖片

並沒有萬能的收集器,選擇的只是對具體應用最適合的收集器。

Serial收集器

Serial收集器是是最基本、歷史最悠久的收集器。這個收集器是單線程的,它在進行垃圾收集時,必須暫停掉所有其他的工作線程,直到它收集結束。運行過程如圖:
技術分享圖片

優點在於它簡單而高效,由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程手機效率。在用戶桌面場景中,分配給虛擬機管理的內存不會很大,所以停頓時間很短,只要不是頻繁發生,這點停頓完全可以接受,所以Serial收集器對於運行在Client模式下的虛擬機來說是一個很好的選擇。

ParNew 收集器

ParNew其實就是Serial收集器的多線程版本。如圖是ParNew收集器的工作過程:

技術分享圖片

它是許多運行在server模式下的虛擬機首選的新生代收集器,一個很重要的原因是,除了Serial收集器外,目前只有它能與CMS收集器配合工作。

可以使用-XX:+UseParNewGC選項來強制指定它。

註意:

後面提到的並發和並行的收集器,可以解釋如下:

  • 並行(Parallel):指多條垃圾收集線程並行工作,但此時用戶線程仍然處於等待狀態。
  • 並發(Concurrent):指用戶線程與垃圾收集線程同時執行(但不一定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集程序運行於另一個CPU上。

Parallel Scanvenge 收集器

Parallel Scanvenge收集器是一個新生代收集器,也是采用復制算法的收集器,又是並行的多線程收集器。

Parallel Scanvenge收集器的特點在於他的目標是達到一個控制的吞吐量。吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)。

停頓時間越短就越適合需要與用戶交互的程序。它提供了兩個參數用於精準控制吞吐量。分別是控制最大垃圾收集時間的-XX:MaxGCPauseMillis參數以及直接設置吞吐量大小的-XX:GCTimeRatio參數。GC停頓時間是以犧牲吞吐量和新生代空間來換取的。

Serial Old 收集器

Serial Old 收集器是Serial收集器的老年代版本,同樣是一個單線程收集器,使用標記整理算法。這個收集器的主要意義也是在於給Cient模式下的虛擬機使用。在Server模式下有兩種用途:一種是在JDK1.5及之前的版本中與Parallel Scanvenge收集器搭配使用。另一種用途是作為CMS收集器的後備預案,在並發收集器發生Concurrent Mode Failure時使用。
技術分享圖片

Parallel Old收集器

Parallel Old是Parallel Scanvenge收集器的老年代版本,使用多線程和“標記-整理”算法。

在註重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scanvenge加Parallel Old收集器。

技術分享圖片

CMS 收集器

CMS(Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。適合互聯網站或者B/S系統的服務端上。

CMS收集器是基於“標記-清楚”算法實現的。收集過程包括四個步驟:

  • 初始標記(CMS initial mark)
  • 並發標記(CMS concurrent mark)
  • 重新標記(CMS remark)
  • 並發清除(CMS concurrent sweep)

其中初始標記、重新標記仍然需要“Stop The World”,初始標記僅僅只是標記GC Roots能直接關聯到的對象,很快。並發標記就是進行GC Roots Tracing的過程。重新標記則是為了修正並發標記期間因為用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,這個階段比初始標記稍長,遠比並發標記短。運行示意圖如下:
技術分享圖片

CMS收集器有三個明顯的缺點

  • CMS收集器對CPU資源非常敏感。
  • CMS收集器無法處理浮動垃圾,可能會出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。由於CMS並發清理階段用戶線程還在運行著,伴隨程序運行還有垃圾不斷產生,這一部分來記出現在標記過程之後,CMS無法在當次收集中處理掉他們,只好留待下一次GC時再清理掉。這一部分垃圾就稱為“浮動垃圾”。
  • 由於CMS收集器基於“標記-清除”算法實現,垃圾收集結束會有大量空間碎片產生。

G1 收集器

G1是一款面向服務端應用的垃圾收集器,未來可以替換掉CMS收集器。與其他收集器相比,特點如下:

  • 並行與並發: G1能充分利用多CPU、多核環境下的硬件優勢,使用多個CPU來縮短Stop-The-World時間。
  • 分代收集,分代概念仍然保留。不需要其他收集器配合就能獨立管理整個GC堆,采取不同的策略。
  • 空間整合:G1從整體來看是基於“標記-整理”算法實現的,從局部來看是基於“復制”算法的,不會產生內存空間碎片。
  • 可預測的停頓,能讓使用這明確指定在一個長度為M毫秒的時間片段內。

G1收集器的運行示意圖:

技術分享圖片

深入理解jvm虛擬機讀書筆記-垃圾收集器與內存分配策略(二)