1、CMS
CMS(Concurrent Mark Sweep,并發標記清除,是為了解決早期垃圾收集器在執行垃圾回收時導致應用程序暫停時間過長的問題而設計的。
CMS的工作流程主要包括以下幾個階段:
-
初始標記(Initial Mark):這個階段是Stop-The-World事件,所有應用線程暫停,垃圾收集器僅標記與根對象直接關聯的對象。這個階段很短。
-
并發標記(Concurrent Mark):在這個階段,應用線程與垃圾收集線程并發執行。垃圾收集器會遍歷整個堆,標記出所有存活的對象。這個過程比較耗時,但因為與用戶線程并發執行,所以不會導致長時間的暫停。
-
重新標記(Remark):這是另一個短暫的Stop-The-World事件,用于修正并發標記期間因用戶程序繼續運行而可能產生的標記變動。相比初始標記,這個階段稍長一些,因為它需要處理整個堆的信息。
-
并發清除(Concurrent Sweep):此階段也是與用戶線程并發執行的,垃圾收集器會清除那些被標記為死亡的對象所占用的內存空間,回收空間供后續使用。
CMS的優點在于其低延遲特性,能夠在大部分垃圾回收工作與應用程序并發執行,從而減少因垃圾回收而導致的暫停時間。然而,CMS也有一些缺點,比如:
- 對CPU資源消耗較高:由于其并發標記和清除的特性,CMS在運行時會占用較多的CPU資源。
- 無法處理浮動垃圾(Floating Garbage):在并發標記和清除階段,新產生的垃圾不能被這次收集處理,需要等到下一次收集。
- 可能出現“Concurrent Mode Failure”:如果在老年代剩余空間不足以容納新生代晉升的對象時,會導致CMS停止并發收集,轉而執行完全的垃圾收集(Full GC),這會導致長時間的應用暫停。
盡管CMS在很長一段時間內被廣泛使用于對延遲敏感的服務,但隨著G1垃圾收集器的發展和完善,CMS在JDK 9中已被廢棄,并在JDK 14中完全移除,推薦使用G1或ZGC、Shenandoah等現代垃圾收集器作為替代。
2、G1
G1垃圾收集器(Garbage First Garbage Collector)是Oracle在Java 7中引入的一種高性能垃圾收集器,旨在替代傳統的CMS(Concurrent Mark-Sweep)垃圾收集器。它結合了多種垃圾收集技術,旨在提供低停頓、高吞吐量和高效的內存管理;
G1垃圾收集器的工作機制和主要算法:
分區(Region)機制
- Region:G1將整個堆劃分成多個大小相等的獨立區域(Region),每個Region通常為1MB到32MB,根據堆的大小自動調整。
- 靈活性:Region可以充當Eden區、Survivor區或老年代的一部分,這種設計使得內存管理更加靈活,有助于優化垃圾收集過程。
年輕代收集(Young GC)
- 復制算法:新生代使用復制算法(Copying),將存活對象從Eden區和一個Survivor區復制到另一個Survivor區。這種方式可以快速整理內存,提高新生代回收的效率。
并發標記周期(Concurrent Marking Cycle)
- 初始標記(Initial Marking):暫停所有應用線程,標記從根對象(GC Roots)直接可達的對象。這個過程時間較短。
- 并發標記(Concurrent Marking):在應用線程運行時,進行可達性分析,標記所有可達的對象。這個過程中不需要暫停應用線程。
- 最終標記(Final Marking):稍微暫停應用線程以完成標記過程,包括處理并發標記階段新創建的對象。
- 篩選回收(Selection Phase):根據之前標記收集的信息,整理并選擇有足夠垃圾的Region進行回收。這個過程會計算每個Region的回收收益,優先清理垃圾最多的Region。
混合收集(Mixed GC)
- 混合GC:這是G1垃圾收集器的特性之一,結合了新生代和老年代的垃圾收集。混合收集不僅回收新生代(Eden和Survivor),同時還會選擇性地回收一部分老年代的Region。
- 標記-壓縮:在混合GC步驟中,G1對老年代的回收通常使用的是標記-壓縮(Mark-Compact)算法。標記階段標記存活的對象,壓縮階段會移動對象來消除內存碎片,從而可以有連續的空間分配給新的大對象。
回收過程中的暫停時間和吞吐量
- 暫停時間:G1垃圾收集器設計了暫停時間預測算法,可以根據用戶配置的最大暫停時間(
-XX:MaxGCPauseMillis
)來適應性調整收集行為。 - 吞吐量:盡量增加應用線程的運行時間,減少垃圾收集的總停頓時間。
總體流程
- 啟動Young GC:當Eden區滿時觸發年輕代收集,使用復制算法將存活對象移動到Survivor區或老年代。
- 并發標記階段啟動:當老年代使用率超過特定閾值時,G1啟動并發標記周期。
- 混合收集:在并發標記周期完成后,會進行多次混合收集,回收新生代和部分老年代。
- 完全垃圾收集(Full GC):作為最后的手段,當其他方法無法騰出足夠空間時,G1會進行Full GC,這是代價最高的一種垃圾收集方式。
通過以上綜合設計,G1垃圾收集器能夠在大堆內存環境中有效地管理內存,降低垃圾收集停頓時間,提高應用的響應性和吞吐量。
3、是否可以理解:G1的內存管理 = CMS + 標記整理
標記-整理算法:在標記-整理算法中,存活對象會被移動以壓縮和消除內存碎片,這個過程需要停止所有應用線程(“Stop-The-World”),導致較長的停頓時間。
雖然CMS可以通過引入標記-整理算法來減少碎片,但無法有效解決長時間停頓和并發處理的復雜性問題。G1通過劃分Region+混合收集,使得G1能夠更加靈活地選擇收集哪些區域,而不是像傳統的分代收集器那樣對整個年輕代或老年代進行回收。
G1混合收集觸發場景
-
周期性觸發:G1有一個稱為“Initiating Heap Occupancy Percent”(IHOP)的閾值,默認情況下,當整個Java堆的使用量達到這個閾值時(默認設置通常是45%到70%之間,可以通過
-XX:InitiatingHeapOccupancyPercent
參數調整),G1會啟動一次混合收集。這意味著G1會定期檢查堆的占用情況,一旦達到設定的閾值,就會觸發一次收集以回收空間。 -
避免內存耗盡:除了基于IHOP的觸發機制外,G1還會根據實際的內存分配速率和當前堆的使用情況動態調整,確保有足夠的空閑空間來滿足應用程序的內存需求,避免因為沒有足夠的空間分配對象而導致的Full GC。
-
目標停頓時間管理:G1在執行時會盡量維持用戶設定的目標停頓時間(通過
-XX:MaxGCPauseMillis
參數指定)。如果系統檢測到為了滿足這個目標停頓時間,需要進行老年代的清理,那么也會觸發混合收集,即使沒有達到IHOP設定的閾值。 -
碎片整理需求:G1在運行過程中會持續監控堆的碎片化程度。如果發現老年代區域過于碎片化,影響了對象分配或者滿足不了預設的內存分配需求,G1也可能決定進行一次混合收集來整理內存,減少碎片。
混合收集的主要目的是回收一部分老年代區域,同時保持暫停時間的可預測性,并通過這種方式平衡吞吐量與響應時間的需求。在整個過程中,G1會優先回收垃圾最多(即回收效益最高的)區域,以提高收集效率。
4、為什么選擇G1
JDK 9之后,G1(Garbage First)垃圾收集器成為了默認的垃圾收集器。這一改變主要基于以下幾點考慮:
-
適應性更強:G1是一種跨代收集器,能夠自動管理整個Java堆,包括年輕代和老年代,而不僅僅是老年代(如同CMS)。
-
更低暫停、更高吞吐:通過并發和并行處理(充分利用多核處理器的能力),以及靈活的Region管理,減少了應用的全局停頓時間,適合對響應時間要求較高的應用。
-
內存管理更高效:通過標記-壓縮算法和區域回收,有效地管理內存碎片,避免大對象分配困難的問題。同時引入了混合收集模式,即在年輕代收集的同時,也會并發地回收一部分老年代,這有助于減少老年代的碎片化問題。
參考:
- https://juejin.cn/post/6844903893906751501
- https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector.htm#JSGCT-GUID-ED3AB6D3-FD9B-4447-9EDF-983ED2F7A573