JVM垃圾收集器
Serial收集器
Serial收集器是最基礎、歷史最悠久的收集器,曾經(在JDK 1.3.1之前)是HotSpot虛擬機新生代 收集器的唯一選擇。
大家只看名字就能夠猜到,這個收集器是一個單線程工作的收集器,但它的“單線 程”的意義并不僅僅是說明它只會使用一個處理器或一條收集線程去完成垃圾收集工作,更重要的是強 調在它進行垃圾收集時,必須暫停其他所有工作線程,直到它收集結束。“Stop The World”這個詞語也 許聽起來很酷,但這項工作是由虛擬機在后臺自動發起和自動完成的,在用戶不可知、不可控的情況 下把用戶的正常工作的線程全部停掉,這對很多應用來說都是不能接受的。讀者不妨試想一下,要是 你的電腦每運行一個小時就會暫停響應五分鐘,你會有什么樣的心情?下圖示意了Serial/Serial Old收集器的運行過程。
每次回收時只有一個線程,因此串行回收器在并發能力較弱的計算機上,其專注性和獨占性的特點往往能讓其有更好的性能表現。
之所以需要“Stop the world"是因為不希望在進行垃圾收集的時候,又產生新的垃圾導致回收不干凈。有一個很好的例子:
比如你在清理垃圾的時候,一邊清掃一邊丟垃圾肯定永遠都掃不干凈,所以需要規定打掃的時候不能扔垃圾,還比如,去公共衛生間時,經常會遇到阿姨在里面打掃垃圾,在外面掛一個牌子——保潔中,暫停使用。
這就是簡單粗暴地Stop the world,雖然簡單粗暴,但是效率也很高,如果上述的打掃只需要幾分鐘或者阿姨保潔只需要幾分鐘,我們也是完全可以接受的。JVM的垃圾收集與此有一定的相似之處,但是又遠比這些簡單的工作要復雜得多。雖然暫停用戶線程的時間一直在優化而減少,但是Serial依然因為其“Stop The World”收到詬病。
但事實上,迄今為止,它依然是HotSpot虛擬機運行在客戶端模式下的默認新生代收集器,有著優于其他收集器的地方,那就是簡單而高效(與其他收集器的單線程相比),對于內 存資源受限的環境,它是所有收集器里額外內存消耗(Memory Footprint)最小的;對于單核處理 器或處理器核心數較少的環境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以 獲得最高的單線程收集效率。在用戶桌面的應用場景以及近年來流行的部分微服務應用中,分配給虛 擬機管理的內存一般來說并不會特別大,收集幾十兆甚至一兩百兆的新生代(僅僅是指新生代使用的 內存,桌面應用甚少超過這個容量),垃圾收集的停頓時間完全可以控制在十幾、幾十毫秒,最多一 百多毫秒以內,只要不是頻繁發生收集,這點停頓時間對許多用戶來說是完全可以接受的。所以,Serial收集器對于運行在客戶端模式下的虛擬機來說是一個很好的選擇。
總結:Serial是一個單線程的垃圾收集器,適用于桌面客戶端應用,其新生代回收采用復制算法,老年代回收采用標記整理法。
ParNew收集器
因為Serial在GC的時候是單線程的,而我們又要盡量地縮短STW(Stop The World)的時間,因此在進行GC的時候可以采用多線程并發的方式進行,而ParNew收集器實質上就是Serial收集器的多線程并行版本,除了同時使用多條線程進行垃圾收集之 外,其余的行為包括Serial收集器可用的所有控制參數(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、對象分配規 則、回收策略等都與Serial收集器完全一致,在實現上這兩種收集器也共用了相當多的代碼。ParNew收 集器的工作過程如下所示
ParNew收集器除了支持多線程并行收集之外,其他與Serial收集器相比并沒有太多創新之處,但它 卻是不少運行在服務端模式下的HotSpot虛擬機,尤其是JDK7之前的遺留系統中首選的新生代收集器,其中有一個與功能、性能無關但其實很重要的原因是:除了Serial收集器外,目前只有它能與CMS收集器配合工作。
總結:ParNew收集器是Serial的并行版本,它是新生代收集器,依然采用復制算法對新生代進行收集,老年代可搭配Serial Old進行收集,因為它能夠搭配CMS收集器,ParNew收集器才成為服務端默認的新生代垃圾收集器。
Parallel Scavenge收集器
Parallel Scavenge收集器也是一款新生代收集器,它同樣是基于標記-復制算法實現的收集器,也是 能夠并行收集的多線程收集器……Parallel Scavenge的諸多特性從表面上看和ParNew非常相似,那它有 什么特別之處呢? Parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是盡可能 地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput)。所謂吞吐量就是處理器用于運行用戶代碼的時間與處理器總消耗時間的比值
Parallel Scavenge收集器設計的目的是讓開發者能夠讓開發者可以控制垃圾收集器的吞吐量。Parallel Scavenge收集器提供了兩個參數用于精確控制吞吐量,分別是控制最大垃圾收集停頓時間 的-XX:MaxGCPauseMillis參數以及直接設置吞吐量大小的-XX:GCTimeRatio參數。
總結:Prallel Scavenge也是一款新生代垃圾收集器,老年代可以搭配Serial Old和Parallel Old進行回收,其特點是可以控制最大垃圾收集停頓時機和吞吐量
Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同樣是一個單線程收集器,使用標記-整理算法。這個收集器的主要意義也是供客戶端模式下的HotSpot虛擬機使用。如果在服務端模式下,它也可能有兩種用途:一種是在JDK 5以及之前的版本中與Parallel Scavenge收集器搭配使用,另外一種就是作為CMS 收集器發生失敗時的后備預案,在并發收集發生Concurrent Mode Failure時使用。
總結:Serial收集器的老年代版本,使用標記-整理算法
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多線程并發收集,基于標記-整理算法實現。這個收集器是直到JDK 6時才開始提供的,在此之前,新生代的Parallel Scavenge收集器一直處于相當尷尬的狀態,原因是如果新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器以外別無選擇,其他表現良好的老年代收集器,如CMS無法與它配合工作。
Parallel Old收集器的工作過程如圖:
直到Parallel Old收集器出現后,“吞吐量優先”收集器終于有了比較名副其實的搭配組合,在注重吞吐量或者處理器資源較為稀缺的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器這個組 合。
總結:Parallel Old是Parallel Scavenge收集器的老年代版本,支持多線程并發收集。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的 Java應用集中在互聯網網站或者基于瀏覽器的B/S系統的服務端上,這類應用通常都會較為關注服務的響應速度,希望系統停頓時間盡可能短,以給用戶帶來良好的交互體驗。CMS收集器就非 常符合這類應用的需求。
從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于標記-清除算法實現的,它的運作 過程相對于前面幾種收集器來說要更復雜一些,整個過程分為四個步驟,包括:
1)初始標記(CMS initial mark)
2)并發標記(CMS concurrent mark)
3)重新標記(CMS remark)
4)并發清除(CMS concurrent sweep)
其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快;并發標記階段就是從GC Roots的直接關聯對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不需要停頓用戶線程,可以與垃圾收集線程一起并發運行;而重新標記階段則是為了修正并發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的 標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但也遠比并發標記階段的時間短;最后是并發清除階段,清理刪除掉標記階段判斷的已經死亡的對象,由于不需要移動存活對象,所以這個階段也是可以與用戶線程同時并發的。
由于在整個過程中耗時最長的并發標記和并發清除階段中,垃圾收集器線程都可以與用戶線程一起工作,所以從總體上來說,CMS收集器的內存回收過程是與用戶線程一起并發執行的,整個過程如下圖:
CMS是一款優秀的收集器,它最主要的優點在名字上已經體現出來:
并發收集、低停頓,一些官 方公開文檔里面也稱之為“并發低停頓收集器”(Concurrent Low Pause Collector)。CMS收集器是HotSpot虛擬機追求低停頓的第一次成功嘗試,但是它還遠達不到完美的程度,至少有以下三個明顯的缺點:
CMS相關的參數:
| -XX:+UseConcMarkSweepGC | boolean | false | 啟用CMS,老年代采用CMS收集器收集 |
| -XX:+CMSScavengeBeforeRemark | boolean | false | 在重新標記前先執行一次Minor GC |
| –XX:-UseCMSCompactAtFullCollection | boolean | false | 對老年代進行壓縮,可以消除碎片,但是可能會帶來性能消耗 |
| -XX:CMSFullGCsBeforeCompaction=n | uintx | 0 | CMS進行n次full gc后進行一次壓縮。如果n=0,每次full gc后都會進行碎片壓縮。如果n=0,每次full gc后都會進行碎片壓縮 |
| –XX:+CMSIncrementalMode | boolean | false | 并發收集遞增進行,周期性把cpu資源讓給正在運行的應用 |
| –XX:+CMSIncrementalPacing | boolean | false | 根據應用程序的行為自動調整每次執行的垃圾回收任務的數量 |
| –XX:ParallelGCThreads=n | uintx | 并發回收線程數量:(ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8) | |
| -XX:CMSInitiatingOccupancyFractio=n | uintx | jdk5 默認是68% jdk6默認92% | 當老年代內存使用達到n%,開始回收 |
注意:有一些參數是聯動的,比如設置了UseCMSCompactAtFullCollection之后,對老年代進行壓縮的參數CMSFullGCsBeforeCompaction才會生效
Garbage First(G1)收集器
Garbage First(簡稱G1)收集器是垃圾收集器技術發展歷史上的里程碑式的成果,它開創了收集器面向局部收集的設計思路和基于Region的內存布局形式。G1提供并發的類卸載的支持,補全了其計劃功能的最后一塊拼圖。這個版本以后的G1收集器才被Oracle官方稱為“全功能的垃圾收集器”(Fully-Featured Garbage Collector)。
G1是一款主要面向服務端應用的垃圾收集器。HotSpot開發團隊最初賦予它的期望是(在比較長 期的)未來可以替換掉JDK 5中發布的CMS收集器。現在這個期望目標已經實現過半了,JDK 9發布之日,G1宣告取代Parallel Scavenge加Parallel Old組合,成為服務端模式下的默認垃圾收集器,而CMS則淪落至被聲明為不推薦使用(Deprecate)的收集器。
作為CMS收集器的替代者和繼承人,設計者們希望做出一款能夠建立起“停頓時間模型”(Pause Prediction Model)的收集器,停頓時間模型的意思是能夠支持指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間大概率不超過N毫秒這樣的目標,這幾乎已經是實時Java(RTSJ)的中軟實時垃圾收集器特征了。
那具體要怎么做才能實現這個目標呢?首先要有一個思想上的改變,在G1收集器出現之前的所有其他收集器,包括CMS在內,垃圾收集的目標范圍要么是整個新生代(Minor GC),要么就是整個老年代(Major GC),再要么就是整個Java堆(Full GC)。而G1跳出了這個樊籠,它可以面向堆內存任何部分來組成回收集(Collection Set,一般簡稱CSet)進行回收,衡量標準不再是它屬于哪個分代,而 是哪塊內存中存放的垃圾數量最多,回收收益最大,這就是G1收集器的Mixed GC模式。
未完待續。。。
本文參考:
【1】本機內容基本摘抄自《深入理解JAVA虛擬機》
【2】部分圖來自bilibili的UP主子爍愛學習的視頻
總結
- 上一篇: 【前沿技术RPA】 一文了解UiPath
- 下一篇: Linux文件目录sha256,如何使用