Java Class Loader 與 Maven Shade Plugin

ChatGPT generated 



Java Class Loader 是 Java Runtime Environment 的一部分,它動態地將 Java 類別加載到 Java Virtual Machine 中。通常,只有在需要時才會加載類別。Java 運行時系統不需要知道文件和文件系統,因為這些都被委派給了類別加載器。

在 Java 語言中,軟體庫通常被打包在 JAR 文件中。庫可以包含不同類型的對象。JAR 文件中包含的最重要類型的對象是 Java 類別。類別可以被視為一個有名稱的程式碼單元。類別加載器負責定位庫,讀取其內容,並加載庫中包含的類別。這種加載通常是 "按需" 進行的,也就是說,直到程式呼叫該類別時才會進行。給定名稱的類別只能被給定的類別加載器加載一次

每個 Java 類別都必須由類別加載器加載。此外,Java 程式可能會使用外部庫(也就是,由程式的作者以外的人寫的和提供的庫),或者至少部分由多個庫組成。

當 JVM 啟動時,會使用三個類別加載器:

  1. Bootstrap class loader
  2. Extensions class loader
  3. System class loader

Bootstrap class loader 加載位於 <JAVA_HOME>/jre/lib(或對於 Java 9 及以上版本,位於 <JAVA_HOME>/jmods)目錄中的核心 Java 庫。這個類別加載器是 JVM 的核心部分,是用原生碼寫的。
Extensions class loader 加載在擴展目錄(<JAVA_HOME>/jre/lib/ext,或由 java.ext.dirs 系統屬性指定的任何其他目錄)中的程式碼。
System class loader 加載在 java.class.path 上找到的程式碼,該路徑映射到 CLASSPATH 環境變量。


當 JVM 需要找到一個類別時,它會遵循以下的步驟:

  1. Bootstrap ClassLoader:首先,Bootstrap ClassLoader 會嘗試在 JVM 的 bootstrap classpath 中找到這個類別。這通常包括像 <JAVA_HOME>/jre/lib/rt.jar 這樣的核心 Java 類別庫。Bootstrap ClassLoader 是用原生碼實現的,並且是 JVM 中的最高級別的類別加載器。
  2. Extension ClassLoader:如果 Bootstrap ClassLoader 找不到該類別,那麼 Extension ClassLoader 會嘗試在擴展目錄(例如 <JAVA_HOME>/jre/lib/ext)中找到這個類別。
  3. Application ClassLoader:如果 Extension ClassLoader 也找不到該類別,那麼 Application ClassLoader(也稱為 System ClassLoader)會嘗試在應用的 classpath 中找到這個類別。這包括由 CLASSPATH 環境變量,-cp 或 -classpath 命令行選項,或者是應用的 Manifest 文件中的 Class-Path 屬性指定的所有目錄和 JAR 文件。

如果所有這些類別加載器都找不到該類別,那麼 JVM 會拋出一個 ClassNotFoundException。

在這個過程中,每個類別加載器都會遵循所謂的 "委託模型"。這意味著在一個類別加載器嘗試加載一個類別之前,它首先會將這個任務委託給它的父類別加載器。這確保了類別加載的唯一性和安全性,因為這樣可以確保核心 Java 類別不能被應用的類別覆蓋。

至於如何決定從哪個 JAR 文件中加載類別,這實際上取決於類別的完全限定名稱以及 JAR 文件在 classpath 中的順序。JVM 會按照 classpath 中的順序,從每個 JAR 文件中嘗試加載類別,直到找到匹配的類別為止。如果有多個 JAR 文件包含同一個類別,那麼 JVM 通常會使用它在 classpath 中首次找到的那個版本。

在 Java 中,class 載入的行為主要由 java.lang.ClassLoader class 定義。這個 class 提供了一個方法 loadClass(String name),它實現了 class 載入的基本機制。這個方法首先檢查該 class 是否已經被載入,如果已經被載入,則直接返回該 class。如果還沒有被載入,則該方法會將載入任務委託給 parent class loader。只有當 parent class loader 無法載入該 class 時,該方法才會嘗試自己載入該 class。


當你遇到多個 JAR 文件包含相同類別的情況:

  1. 檢查並更新你的 CLASSPATH:確保 CLASSPATH 中的 JAR 文件順序正確,並且你需要的類別版本在前面的 JAR 文件中。
  2. 移除重複的 JAR 文件:如果可能,從你的應用和 CLASSPATH 中移除包含重複類別的 JAR 文件。
  3. 使用 Maven 或 Gradle 這樣的依賴管理工具:這些工具可以幫助你管理你的依賴並避免版本衝突。例如,Maven 的 "dependency:tree" 命令可以幫助你找出哪些依賴包含了相同的類別。
  4. 使用 Maven Shading Plugin:這個插件可以將你的應用的所有依賴打包到一個 uber-JAR 中,並且可以選擇性地將包名重新命名(或 "shade")以避免命名空間衝突。
  5. 使用 OSGi 或 Java 9 的模組系統:這些技術可以讓你更精細地控制哪些類別和 JAR 文件被加載。
  6. 使用自定義的類別加載器:在某些情況下,你可能需要實現你自己的類別加載器來解決這個問題。這通常需要較深的 Java 知識,並且可能會引入其他的複雜性。

Maven shading plugin example
  1. 這個 repo 包含兩個 Maven module:example-lib 和 example-main。
  2. 兩個 module 都使用了 Google 的 Guava lib,但是版本不同。example-lib 使用的是 Guava 31,而 example-main 使用的是 Guava 16。
  3. 在 example-lib module 中,有一個名為 Preconditions31 的 class,它使用了 Guava 31 的 Preconditions class。
  4. 在 example-main module 中,有一個名為 Hello 的 class,它同時使用了 Guava 16 的 Preconditions class 和 example-lib module 中的 Preconditions31 class。
  5. 這個 repo 的主要目的是演示如何使用 Maven 的 Shade 插件來解決 JAR 包中的 class 衝突問題。
  6. 在這個例子中,Shade 插件被用來將 example-lib module 中的 Guava 31 的 Preconditions class 重新命名為 shaded.example.lib.Preconditions31。
  7. 通過這種方式,example-main module 可以同時使用 Guava 16 和 Guava 31 的 Preconditions class,而不會產生衝突。

Scrum Note

ChatGPT generated


 Scrum 是一種敏捷軟體開發的框架,它的核心包含了三個支柱 (Pillars)、五個價值觀 (Values) 和十個原則 (Principles)。


三個支柱 (Pillars) 包括:


  1. 透明度 (Transparency):所有的工作都必須對所有人可見,這樣才能確保所有人都對工作的進度有共識。
  2. 檢驗 (Inspection):Scrum 團隊必須定期檢查進行中的工作和工作成果,以確保向著目標前進。
  3. 調整 (Adaptation):如果在檢驗過程中發現實際的結果與預期的結果有偏差,團隊必須調整他們的行為或者計劃。

五個價值觀 (Values) 包括:


  1. 承諾 (Commitment):團隊成員對於達成共同目標的承諾。
  2. 勇氣 (Courage):面對困難和挑戰時,團隊成員需要有勇氣去做出改變。
  3. 專注 (Focus):團隊成員專注於工作和目標,並且優先處理最重要的事情。
  4. 開放 (Openness):團隊成員對於工作、挑戰和進度要保持開放的態度。
  5. 尊重 (Respect):團隊成員相互尊重,認識到每個人都是獨一無二的,並且有他們自己的技能和能力。

敏捷宣言 (Agile Manifesto) 的四個核心價值包括:


  1. 個人和互動高於流程和工具 (Individuals and interactions over processes and tools)
  2. 可用的軟體高於詳盡的文件 (Working software over comprehensive documentation)
  3. 客戶合作高於合約談判 (Customer collaboration over contract negotiation)
  4. 回應變化高於遵循計劃 (Responding to change over following a plan)

敏捷宣言的十二個原則包括:

  1. 我們最高優先的是透過提早並持續地交付有價值的軟體來滿足客戶。
  2. 即使在開發的後期,我們也歡迎改變需求。敏捷流程利用變化為客戶獲得競爭優勢。
  3. 經常地交付可工作的軟體,交付的頻率可以從幾週到幾個月,交付的時間間隔越短越好。
  4. 業務人員和開發人員必須每天都要一起工作,直到項目完成。
  5. 建立項目周圍的人們的熱情,給他們所需要的環境和支持,並且信任他們能完成工作。
  6. 最有效且最有效率的傳遞信息的方法,就是面對面的交談。
  7. 可工作的軟體是進度的主要度量標準。
  8. 敏捷流程提倡可持續的開發。贊助者、開發者和使用者應該能夠保持恆定的步伐。
  9. 持續關注技術優秀性和良好的設計增強敏捷性。
  10. 簡單——使未完成的工作最大化的藝術——是必要的。
  11. 最好的架構、需求和設計出自自組織的團隊。
  12. 團隊定期反思如何能夠更有效,然後調整和改變其行為。

刺蝟原則 (Hedgehog Concept) 是由吉姆·柯林斯 (Jim Collins) 在他的書《從優秀到偉大》(Good to Great) 中提出的。這個概念源於古希臘詩人伊索的寓言,故事中的狐狸雖然有許多聰明的策略來捕捉刺蝟,但刺蝟只做一件事,且做得很好:捲成一個球來保護自己。柯林斯將這個故事用來形容公司和個人應該專注於他們可以做得最好的事情。


刺蝟原則包含三個部分:

  1. 你最熱衷什麼?(What are you deeply passionate about?)
  2. 你在什麼能力上可以成為世界最頂尖的人?(What can you be the best in the world at?)
  3. 你的經濟引擎是什麼?(What drives your economic engine?)

Scrum 定義了五種主要的會議,每種都有其特定的目標和結構:


  1. Sprint 規劃會議 (Sprint Planning Meeting):在這個會議中,團隊會決定下一個 Sprint 要完成的工作。產品擁有者 (Product Owner) 會解釋他們希望團隊在下一個 Sprint 中完成的項目,並且團隊會決定他們能夠完成多少工作。
  2. 每日 Scrum 會議 (Daily Scrum Meeting):這是一個每天都會進行的短會議,通常只會持續 15 分鐘。在這個會議中,團隊會分享他們在前一天完成了什麼,今天計劃完成什麼,以及是否有任何阻礙他們工作的問題。
  3. Sprint 審查會議 (Sprint Review Meeting):在每個 Sprint 結束時,團隊會舉行一個審查會議,以展示他們在 Sprint 中完成的工作。這是一個讓所有利益相關者了解項目進度的機會。
  4. Sprint 回顧會議 (Sprint Retrospective Meeting):這是在每個 Sprint 結束後的一個會議,團隊會討論在過去的 Sprint 中什麼工作得,什麼不好,以及他們可以如何改進。
  5. 產品 Backlog 整理會議 (Product Backlog Refinement Meeting):這是一個可選的會議,用於審查和更新產品 Backlog。在這個會議中,團隊會評估 Backlog 項目的優先順序,並可能將大的項目分解成更小、更可管理的項目。

常見的對 Scrum 的詬病


  1. 過度的會議和管理:Scrum 需要定期的 Sprint 規劃、每日 Scrum、Sprint 審查和回顧會議。對於一些團隊來說,這可能會感覺像是過度的會議和管理,並可能導致工作時間的浪費。
  2. 難以應對變化:雖然 Scrum 是為了應對變化而設計的,但在 Sprint 進行中改變計劃可能會很困難。這可能會導致團隊在面對變化時感到壓力,或者在需要快速反應時無法做出適應。
  3. 過度依賴 Scrum Master:Scrum Master 的角色是幫助團隊遵循 Scrum 的規則和實踐,並解決阻礙團隊進步的問題。然而,如果團隊過度依賴 Scrum Master,可能會降低團隊的自我組織能力。
  4. 忽視技術實踐:Scrum 並未明確包含如測試驅動開發 (TDD)、持續集成 (CI)、重構等技術實踐。如果團隊僅僅遵循 Scrum 的流程,而忽視了這些重要的技術實踐,可能會導致軟體質量的問題。
  5. Scrum 不是萬能藥:Scrum 並不能解決所有的問題。它是一種工具,需要根據特定的情況和團隊來適當地使用。如果盲目地遵循 Scrum,而不考慮其是否適合特定的情況,可能會導致效果不佳。

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 作為權重. 解法 別名演算法...