Java GC Note

CMS GC


Java 中的垃圾收集(Garbage Collection)的基本概念。在 Java 中,當我們創建對象時,這些對象會被存儲在一個名為堆(Heap)的內存區域中。當這些對象不再被使用時,它們就變成了"垃圾",需要被清理出堆,以釋放空間供新的對象使用。這就是垃圾收集的工作。

CMS GC 是 Java 中的一種垃圾收集器,它的主要目標是減少垃圾收集導致的應用程序停頓時間。它的工作流程大致如下:

  1. 初始標記(Initial Mark):這是一個需要停止所有應用程序線程的階段,也就是我們所說的 "Stop-The-World"。在這個階段,GC 會標記出所有從根(Root)開始直接可達的對象。
  2. 並行標記(Concurrent Mark):在這個階段,GC 會在應用程序線程運行的同時,標記出所有從根對象開始間接可達的對象。這個階段的名稱 "並行",就是指 GC 能夠與應用程序線程同時運行。
  3. 重新標記(Remark):這也是一個 "Stop-The-World" 階段。因為在並行標記階段,應用程序線程還在修改對象引用,所以可能會有一些新的可達對象被遺漏,或者有一些已經不再可達的對象被錯誤地標記。因此,GC 需要再次停止所有應用程序線程,對這些對象進行重新標記。
  4. 並行清除(Concurrent Sweep):在這個階段,GC 會清除掉所有未被標記的對象,也就是不再被應用程序使用的對象。這個階段也是並行進行的,不會影響應用程序的運行。


在 CMS GC 中,一個物件可能會經歷以下的生命週期:

物件創建:當你在 Java 程式中創建一個新的物件,它會被存放在 Heap 中。
物件使用:在物件被創建後,你的程式可能會使用這個物件,這可能包括讀取物件的屬性,調用物件的方法,或者將物件作為參數傳遞給其他方法。
物件變為垃圾:當你的程式不再使用一個物件,並且沒有任何引用指向這個物件時,這個物件就變成了垃圾。這意味著這個物件不再可達,並且可以被垃圾收集器清理。
垃圾收集:在 CMS GC 的運作過程中,這個物件會被標記為垃圾,並在適當的時候被清理。如果物件在並行標記階段被標記,那麼它將在並行清除階段被清理。如果物件在重新標記階段被標記,那麼它將在下一個並行清除階段被清理。
物件清理:當物件被清理時,它佔用的內存空間將被釋放,並可以被用來存放新的物件。在這個過程中,物件的終結器(如果有的話)將被調用。

  1. 並行標記清除 GC (CMS)
    • 這是為了偏好較短的垃圾收集暫停的應用程式而設計的。
    • 它適合互動式的應用程式。
    • CMS GC 使用多個 GC 線程來掃描 heap。
    • 它的大部分工作是與應用程式線程並行進行的。
  2. Heap 結構
    • Heap 被劃分為:Eden, Survivor (S0 和 S1), Old。
    • 物件最初在 Eden 中分配。
    • 每次 Minor GC 後,活著的物件被移動到一個 Survivor 空間。
    • 在 Survivor 空間中生存足夠長時間的物件被移動到 Old generation。
  3. GC 過程
    • Minor GC:清理 Eden 和 Survivor 空間。
    • Major GC (也被稱為 Concurrent Mark Sweep GC):清理 Old generation。
    • Major GC 是並行的,這意味著它在應用程式運行的時候發生。
    • 如果 Old generation 變滿,將會發生 Full GC,這是一個停止世界的事件。
  4. 調整
    • CMS GC 是可調的,允許你在吞吐量和暫停時間之間取得平衡。
    • 重要的選項包括:-XX:ParallelCMSThreads, -XX:CMSInitiatingOccupancyFraction。

不過 CMS GC 執行久了會有記憶體碎片化的問題, 這問題主要來自於它的垃圾收集策略。CMS GC 是一種以減少暫停時間為目標的垃圾收集器,它的主要工作是在應用程式運行的同時進行垃圾收集。為了達到這個目標,CMS GC 在大部分時間內都避免進行記憶體整理。
在 CMS GC 中,當物件被回收時,它們佔用的記憶體空間會被釋放,但並不會立即被重新使用。這些釋放的記憶體空間會形成一個個的"空洞",這些"空洞"分散在整個堆中,形成了所謂的"碎片"。隨著時間的推移,這些碎片可能會變得越來越多,導致記憶體碎片化。
記憶體碎片化可能會導致一些問題。例如,當應用程式需要分配一個大物件時,可能會找不到足夠大的連續記憶體空間,即使整體的可用記憶體空間是足夠的。在這種情況下,CMS GC 可能需要進行一次完全的垃圾收集和記憶體整理,這會導致一次長時間的暫停。

雖然 CMS GC 會把物件在 S0 和 S1(Survivor 0 和 Survivor 1)搬移,但它們是用於物件年齡管理和晉升的。在 Minor GC 中,活著的物件會在 Eden、S0 和 S1 之間移動,並最終被晉升到 Old Generation。這個過程確實會進行一些記憶體整理,但這只影響到 Young Generation,並不能解決 Old Generation 的記憶體碎片化問題。

STW - Stop the world


"Stop the World" 是 Java 垃圾收集中的一個術語。當垃圾收集器需要進行某些工作時,它會要求所有的應用程式線程(也就是你的 Java 程式正在執行的線程)暫時停止執行,這就是所謂的 "Stop the World"。

你可以將其想像成在公路上的交通管制。當有重要的車輛(例如警車或救護車)需要通過時,所有其他的車輛都必須停下來,讓重要的車輛先行。在這個比喻中,重要的車輛就像垃圾收集器,而其他的車輛就像應用程式的線程。

"Stop the World" 的時間通常會盡量短,以減少對應用程式的影響。但是,如果垃圾收集器需要清理大量的垃圾,或者需要進行大規模的內存整理,那麼 "Stop the World" 的時間可能會比較長。這就是為什麼在設計和選擇垃圾收集器時,我們需要考慮到 "Stop the World" 的影響。

在 CMS (Concurrent Mark Sweep) GC 中,Eden Space 的 Minor GC 是 Stop-The-World (STW) 的主要原因是為了簡化設計和提高效率。

  1. 簡化設計:Minor GC 是一個複雜的過程,它需要確定哪些物件是活著的,哪些物件是死的,並且需要更新所有活著的物件的引用。如果在這個過程中允許應用程式並行運行,那麼就需要處理應用程式可能會改變物件狀態和引用的複雜情況。這會使得 GC 的設計和實現變得更加複雜。因此,為了簡化設計,選擇在 Minor GC 時暫停應用程式。
  2. 提高效率:Minor GC 通常只處理 Eden Space,這是一個相對較小的區域,並且由於 Java 的物件分配模型,大部分物件在 Eden Space 中不會存活很長時間。因此,Minor GC 通常可以很快完成。如果在這個過程中允許應用程式並行運行,那麼就需要付出額外的同步和協調的代價,這可能會降低 GC 的效率。

因此,儘管 STW 會導致應用程式的暫停,但在 Eden Space 的 Minor GC 中使用 STW 是一種權衡和妥協的結果。

而 CMS (Concurrent Mark Sweep) GC 的 Remark Phase 是 Stop-The-World (STW) 的原因主要是為了確保堆中所有的物件都被正確地標記。

在 CMS GC 的 Concurrent Marking Phase(並行標記階段)中,GC 會在應用程式運行的同時標記堆中的物件。然而,由於應用程式在運行,所以可能會有新的物件被創建,或者已經存在的物件的引用關係被改變。這些變化可能會導致一些物件的標記狀態變得不正確。

為了解決這個問題,CMS GC 在 Concurrent Marking Phase 之後會進行 Remark Phase(重新標記階段)。在這個階段中,GC 會暫停應用程式,並重新掃描堆中的物件,確保所有的物件都被正確地標記。這就是為什麼 Remark Phase 是 STW 的。

雖然 Remark Phase 是 STW 的,但是由於它只需要處理在 Concurrent Marking Phase 中變化的部分,所以通常可以很快完成。這樣,CMS GC 可以在保證正確性的同時,盡可能地減少 STW 的時間。

G1GC


CMS (Concurrent Mark Sweep) GC 和 G1 (Garbage-First) GC 都是針對減少垃圾收集暫停時間的垃圾收集器。然而,它們在設計和實現上有一些重要的區別,這些區別可能會影響到你選擇哪一種垃圾收集器。以下是一些可能需要從 CMS GC 轉換到 G1 GC 的情況:

  1. 大堆需求:如果你的應用程式需要一個非常大的堆(例如,數十GB或更大),那麼 G1 GC 可能是一個更好的選擇。G1 GC 的設計目標是支持大堆,並且能夠預測垃圾收集的暫停時間。相比之下,CMS GC 的暫停時間可能會隨著堆的大小增加而增加。
  2. 預測性暫停時間:如果你需要更好的控制垃圾收集的暫停時間,那麼 G1 GC 可能是一個更好的選擇。G1 GC 提供了一個暫停時間目標,你可以設定這個目標,讓 G1 GC 儘可能地達到這個目標。
  3. 更高的吞吐量:雖然 CMS GC 在減少暫停時間方面表現出色,但是它可能會導致吞吐量下降,因為它需要在垃圾收集和應用程式之間進行更多的上下文切換。相比之下,G1 GC 通常可以提供更高的吞吐量。
  4. 更好的記憶體整理:CMS GC 可能會導致記憶體碎片化,特別是在長時間運行的應用程式中。當記憶體碎片化變得嚴重時,CMS GC 可能會導致不可預測的長暫停時間。相比之下,G1 GC 透過定期的整理操作,可以更好地處理記憶體碎片化問題。

G1GC(Garbage-First Garbage Collector)是Java的一種垃圾收集器,它是為了滿足大數據量和低延遲需求而設計的。
以下是G1GC的主要特點和工作原理:

分區收集:G1GC將Java堆分成多個相同大小的區域(Region),每個區域可能是Eden區、Survivor區或Old區。這種分區方式使得G1GC能夠獨立並且並行地收集每個區域,從而減少了單次垃圾收集的停頓時間。
Garbage-First:G1GC的名稱中的"Garbage-First"意味著它優先收集垃圾最多的區域。這種策略使得G1GC能夠最大限度地釋放空間,並且在有限的時間內達到最好的垃圾收集效果。
可預測的停頓時間:G1GC允許你指定最大的垃圾收集停頓時間,它會儘可能地在這個時間限制內完成垃圾收集。這種特性使得G1GC非常適合需要低延遲的應用。
並行和並行階段:G1GC在執行垃圾收集時,有一些階段可以與應用程序線程並行運行,這可以減少垃圾收集對應用程序的影響。
記憶體壓縮:G1GC有一個稱為"記憶體壓縮"的階段,它會在背景中將活躍對象從一個區域移動到另一個區域,從而釋放更多的空間。
全面垃圾收集:當Java堆中的空間不足,或者垃圾收集無法在指定的停頓時間內完成時,G1GC會執行一次全面的垃圾收集(Full GC)。全面垃圾收集會停止所有的應用程序線程,並且收集整個Java堆中的垃圾。

  1. G1 GC
    • G1 GC 的設計目標是降低停頓時間並適應大記憶體。
    • 它適合需要大記憶體和短停頓時間的應用程式。
  2. Heap 結構
    • Heap 被分成多個 Region。
    • 每個 Region 可以是 Eden, Survivor 或 Old。
  3. GC 過程
    • Minor GC:清理 Eden 和 Survivor。
    • Major GC:清理 Old。
    • Major GC 進行並行標記和清除。
    • 如果需要,會有停頓世界的事件。
  4. 調整
    • G1 GC 是可調的。
    • 重要選項包括:-XX:MaxGCPauseMillis, -XX:G1NewSizePercent, -XX:G1MaxNewSizePercent, -XX:G1HeapRegionSize。

Shenandoah GC

Shenandoah GC 是一種低暫停時間的垃圾收集器,它透過與 Java 程式並行執行更多的垃圾收集工作來減少 GC 暫停時間。Shenandoah 的大部分 GC 工作都是並行進行的,包括並行壓縮,這意味著它的暫停時間不再直接與堆大小成比例。無論是對 200 GB 的堆進行垃圾收集,還是對 2 GB 的堆進行垃圾收集,都應該具有相似的低暫停行為。

在 Shenandoah GC 中,一個物件的生命週期會經歷以下階段:

  1. 初始化標記 (Init Mark):這個階段會開始並行標記,準備堆和應用程式線程進行並行標記,然後掃描根集。這是循環中的第一個暫停,最主要的時間消耗者是根集掃描。因此,其持續時間取決於根集的大小。
  2. 並行標記 (Concurrent Marking):這個階段會遍歷堆,並追蹤可達物件。這個階段與應用程式並行運行,其持續時間取決於活動物件的數量和堆中物件圖的結構。由於應用程式在此階段可以分配新數據,因此在並行標記期間,堆佔用率會上升。
  3. 最終標記 (Final Mark):這個階段會完成並行標記,通過排空所有待處理的標記/更新隊列並重新掃描根集。它還通過找出要撤離的區域(收集集),預撤離一些根,並一般準備運行時進入下一階段。這是循環中的第二個暫停,這裡的最主要時間消耗者是排空隊列和掃描根集。
  4. 並行清理 (Concurrent Cleanup):這個階段會回收立即垃圾區域,也就是在並行標記後檢測到沒有活動物件存在的區域。
  5. 並行撤離 (Concurrent Evacuation):這個階段會從收集集中的活動物件撤離到新區域。這個階段與應用程式並行運行,其持續時間取決於收集集中的活動物件數量。
  6. 初始化更新引用 (Init Update Refs):這個階段會準備堆和應用程式線程進行並行更新引用,然後掃描根集。這是循環中的第三個暫停,最主要的時間消耗者是根集掃描。
  7. 並行更新引用 (Concurrent Update Refs):這個階段會遍歷堆,並更新物件引用以指向撤離物件的新位置。這個階段與應用程式並行運行,其持續時間取決於堆中的物件數量。
  8. 最終更新引用 (Final Update Refs):這個階段會完成並行更新引用,通過排空所有待處理的更新隊列並重新掃描根集。這是循環中的第四個暫停,最主要的時間消耗者是排空隊列和掃描根集。
  9. 並行清理 (Concurrent Cleanup):這個階段會回收所有剩餘的垃圾區域,也就是在並行更新引用後檢測到沒有活動物件存在的區域。

這就是 Shenandoah GC 中一個物件的生命週期。在這個過程中,物件會被標記、撤離、更新引用,並最終被清理。這個過程的大部分階段都與應用程式並行運行,從而實現低暫停時間的垃圾收集。

在垃圾收集過程中,特別是在物件撤離(Evacuation)階段,物件可能會被移動到堆的其他位置。如果在物件被移動的同時,應用程式試圖讀取該物件的引用,那麼它可能會讀取到一個無效的引用,因為該物件已經不再位於原來的位置。
為了解決這個問題,Shenandoah GC 在應用程式讀取物件引用時插入了一個 "Read Barrier"。這個 "Read Barrier" 會檢查該物件是否正在被撤離。如果是,那麼 "Read Barrier" 會先等待撤離完成,然後返回物件新的位置。這樣,應用程式就可以正確地讀取物件的引用,即使該物件正在被移動。
"Read Barrier" 是一種低開銷的操作,因為它只需要檢查物件的撤離狀態,並且只有在物件正在被撤離時才需要等待。因此,它不會對應用程式的性能產生顯著影響。

Shenandoah GC 也有一些階段會產生 Stop-The-World (STW) 暫停,但這些暫停的時間通常非常短,並且與堆的大小無關。這是因為 Shenandoah GC 的設計目標是使大部分的垃圾收集工作都能與應用程式並行進行。

在 Shenandoah GC 中,以下四個階段會產生 STW 暫停:
初始化標記 (Init Mark):這個階段會開始並行標記,準備堆和應用程式線程進行並行標記,然後掃描根集。
最終標記 (Final Mark):這個階段會完成並行標記,通過排空所有待處理的標記/更新隊列並重新掃描根集。
初始化更新引用 (Init Update Refs):這個階段會準備堆和應用程式線程進行並行更新引用,然後掃描根集。
最終更新引用 (Final Update Refs):這個階段會完成並行更新引用,通過排空所有待處理的更新隊列並重新掃描根集。

這些 STW 暫停的時間主要取決於根集的大小,而不是整個堆的大小。根集是指那些可能引用堆中其他物件的物件,通常包括全局變數、執行緒堆疊等。根集的大小通常遠小於整個堆的大小,因此這些 STW 暫停的時間通常非常短。
至於 "更新引用" 的階段,這個階段是與應用程式並行進行的。在這個階段中,Shenandoah GC 會遍歷堆,並更新物件引用以指向撤離物件的新位置。由於這個階段是與應用程式並行進行的,所以它不會產生 STW 暫停,並且其持續時間與堆的大小無關。


G1 (Garbage-First) GC 和 Shenandoah GC 都是針對減少垃圾收集暫停時間的垃圾收集器,但它們在設計和實現上有一些重要的區別。以下是一些可能需要從 G1 GC 轉換到 Shenandoah GC 的情況:

  1. 更低的暫停時間:如果你的應用程式對垃圾收集的暫停時間有非常嚴格的要求,那麼 Shenandoah GC 可能是一個更好的選擇。Shenandoah GC 的設計目標是提供更低的暫停時間,它的大部分工作都是與應用程式並行進行的,包括壓縮(Compaction)。
  2. 大堆需求:如果你的應用程式需要一個非常大的堆(例如,數十GB或更大),那麼 Shenandoah GC 可能是一個更好的選擇。Shenandoah GC 的暫停時間與堆的大小無關,這使得它更適合於大堆的情況。
  3. 更好的記憶體整理:如果你的應用程式有大量的長生命周期物件,或者有大量的大物件,那麼可能會遇到記憶體碎片化的問題。在這種情況下,Shenandoah GC 可能是一個更好的選擇,因為它可以在應用程式運行的同時進行記憶體整理。

在應用程式使用非常大的記憶體時
G1 GC 確實可以處理大型堆(例如數十GB或更大),並且它的設計目標就是為了更好地處理大型堆。然而,與 Shenandoah GC 相比,G1 GC 在處理大型堆時可能會有一些限制。
G1 GC 的暫停時間通常與堆的大小有關。這是因為 G1 GC 在執行全局垃圾收集(也稱為"Stop-The-World"收集)時,需要遍歷整個堆。因此,堆越大,全局垃圾收集所需的時間就越長。
另一方面,Shenandoah GC 的設計目標是使暫停時間與堆的大小無關。這是因為 Shenandoah GC 的大部分工作(包括標記和整理)都是與應用程式並行進行的。因此,即使堆非常大,Shenandoah GC 的暫停時間也可以保持在一個較低的水平。
因此,如果你的應用程式需要一個非常大的堆,並且你希望垃圾收集的暫停時間盡可能地低,那麼 Shenandoah GC 可能是一個更好的選擇。然而,這並不意味著 G1 GC 不能處理大型堆,只是在某些情況下,Shenandoah GC 可能會表現得更好。

沒有留言:

張貼留言

別名演算法 Alias Method

 題目 每個伺服器支援不同的 TPM (transaction per minute) 當 request 來的時候, 系統需要馬上根據 TPM 的能力隨機找到一個適合的 server. 雖然稱為 "隨機", 但還是需要有 TPM 作為權重. 解法 別名演算法...