1. 程式人生 > >💕《給產品經理講JVM》:垃圾收集器

💕《給產品經理講JVM》:垃圾收集器

前言

在上篇中,我們把 JVM 中的垃圾收集演算法有了一個大概的瞭解,又是一個陰雨連綿的週末,宅在家裡的我們又開始了新一輪的學習:

產品大大:上週末我們說了垃圾收集演算法,下面是不是要講一下這些演算法的應用呢?

我:當然,如果說垃圾收集演算法是打狗棒法,那麼垃圾收集器就是歷屆的丐幫幫主們,不同的幫主領悟到的自然也就不同,我先對這些幫主進行一個簡單的介紹,看圖!

我:我從回收的區域去對垃圾收集器進行了一個簡單的劃分,大致可以分為這樣九種,下面就且聽我為你一一道來。

產品大大:好噠

Serial 收集器

我:下面要說的就是一款非常古老的「新生代收集器」—— Serial 收集器,它的歷史可以追溯到 JDK 還是以1.x命名的時代(大概時間在我五歲的時候?

產品大大:從 Serial 這個名字上可以看的出,這應該是一款序列(單執行緒)收集器,對吧?

我:沒錯,Serial 收集器是一款單執行緒的收集器,它的收集過程也很簡單(如下圖),雖然它只會使用一個處理器或一條執行緒去收集垃圾,但是這裡我不喜歡稱之為單執行緒收集器,它更適合「序列」這個詞,因為它在進行 GC 的時候,必須暫停掉其他的執行緒(Stop The World,STW)

產品大大:那麼這個收集器是不是已經廢棄掉了啊,僅僅停留在歷史中,作為一個值得去紀念的東西?

我:其實也並不能這麼說,雖然它簡單,但是簡單有簡單的好處,因為單執行緒,所以沒有切換執行緒的資源開銷,所以在單核CPU的環境下,它的表現反而會顯得更加優秀,只能說是沒有最好的收集器,只有最適合的收集器~

產品大大:醬紫哦,那麼我們來說下一個收集器吧,這個我已經有一個大概的瞭解了~

ParNew 收集器

我:下一個收集器啊,叫做 ParNew 收集器,這個收集器和上面我們所說的 Serial 收集器基本上一模一樣,除了它會啟動多個GC執行緒去進行垃圾的收集(並行收集)

產品大大:並行?我只聽過併發哦。。

我:併發指的是工作執行緒與垃圾收集執行緒之間的關係,而並行指的是多條垃圾收集執行緒之間的關係,這兩個的概念是不一樣的,並行的時候,工作執行緒處於等待狀態。而併發的時候,工作執行緒處於活動狀態。

產品大大:那麼它和 Serial 收集器比起來有什麼區別呢?

我:在單執行緒環境下,Serial 的 GC 效率是要優於它的,但是在多執行緒環境下,它對於垃圾收集時系統資源的高效利用還是很是頗有成效的。

產品大大:那麼它現在還好嘛~

我:它在 HotSpot 虛擬機器中是第一款退出了歷史舞臺的收集器,在 JDK 9 的時候,把它等於併入了 CMS 收集器中,而且來一個小小的劇透。。在 JDK 14 中 CMS也被刪除掉,從輝煌到落幕,正式結束了在服務端稱霸的生涯。

產品大大:好可憐,嗚嗚嗚嗚

我:???停止你的表演,我們繼續

Parallel Scavenge 收集器

我:下面要說的這個也是一個新生代的收集器——Parallel Scavenge 收集器,從表面上來看,它好像和 ParNew 收集器之間沒有太大的區別,但是為什麼我們要它單獨列出來呢?

產品大大:這是在考我嘛?

我:說說看咯

產品大大:雖然這倆貨看起來差不多,但是我覺得就像微信和 QQ 的區別一樣,這倆貨雖然看起來也是很類似,但是它們的區別就在於側重點不一樣,細分市場的不同會讓它們雖然有所衝突,但是不會有你死我活的那樣惡性的競爭關係,而更偏向於互補,陰陽相生。

我:說的真好,這嘴真能忽悠,怪不得天天開發寫不完的需求(默默碎碎念一百句

我:它更側重於吞吐量,吞吐量的意思是什麼呢,它並不是非常 Care 每次STW的間隔時間,而是更看重對資源的一個整體利用率。

產品大大:那麼我們如何去控制吞吐量呢?

我:有這麼兩個引數:-XX:MaxGCPauseMillis 和 -XX:GCTimeRatio。第一個是用來控制每次 STW 的時間,第二個是直接設定吞吐量的大小,還有一個引數:-XX:+UseAdaptiveSizePolicy,這個引數是一個布林值,如果開啟這個開關,虛擬機器會自動的去調節前兩個引數的大小,可以達到一個自適應的效果。這個引數也是一個具有Parallel Scavenge特色的,區別於 ParNew 的所在。

Serial Old 收集器

我:新生代的收集到這就告一段落了,下面說一下老年代的三個收集器,第一個是 Serial 收集器的老年代版本——Serial Old 收集器,如其名那樣,它和它的新生代收集器—— Serial 收集器一樣,是一個單執行緒的收集器,和新生代的 Serial 不同,它採用的是標記整理演算法。

產品大大:這情侶 id 嘖嘖,我已經想象到了看到這倆名字的程式猿,冷冷的狗糧往嘴裡胡亂的塞

我:咳咳,雖然這倆是情侶 id ,但是它可以和 Parallel Scavenge 組cp,而且它還是 CMS 的一個備胎。。

產品大大:??hetui,男人沒有一個好東西,下一個吧下一個

我:??(關我毛事兒。。人在家中坐,鍋從天上來

Parallel Old 收集器

我:好了,我們不提剛剛的那個小插曲,下面我們來說一下 Parellel Old 這款老年代的收集器,看這個名字,我們可以知道這款收集器和 Parellel Scavenge 收集器是一家子,它正是 Parallel Scanvenge 的老年代版本,在它出現之前,Serial Old 一直充當著他的角色與 Parallel Scanvenge 進行配合,但是它出來之後,算是把那個小三給趕走了,後來居上的成為了原配。

產品大大:原來這個就是原配哦,也就是說如果我們看重吞吐量的話,就可以使用它們的 ‘夫妻’ 組合去完成(夫妻搭配,幹活不累

我:是這樣的,沒錯呢,下面我們來介紹一個重量級的收集器,它在 JVM 的垃圾收集器歷史中劃下了一個濃墨重彩的一筆。

產品大大:好啊好啊~

CMS 收集器

我:下面要介紹的這款收集器叫做—— CMS(Concurrent Mark Sweep)收集器,為什麼說它是一款劃時代意義的收集器呢,它是第一款將併發這個詞詮釋出意義並獲得大範圍的應用和推廣的收集器,就是說,它可以做到一邊丟垃圾,一邊收垃圾。

產品大大:那麼它的執行流程是什麼呢,一定和其他有所不同吧~

我:看圖~

我:首先由它的名字我們可以知道,它是基於標記——清除演算法的,而它分為了四步走:

  1. 初始標記(這一過程耗時很短,只是標記一下GC Roots 能直接關聯到的物件
  2. 併發標記(和使用者執行緒一起執行,雖然耗時較長,但是不會造成STW
  3. 重新標記(修正在併發標記過程中的變動,耗時比初始長,但是遠小於併發標記時間
  4. 併發清除(和使用者執行緒一起執行,進行併發的清除

產品大大:那麼 CMS 既然這麼優秀,是不是我們一直都在使用這個呢?

我:並不然,一般劃時代的下場都不會很好,因為它存在了三個問題:

  1. 對處理器的資源耗費較大,比較敏感
  2. 在GC的過程中,容易產生新的垃圾,浮動垃圾的產生如果導致併發失敗,就會啟用Serial Old來進行,這樣就很慢很慢了
  3. 由於它是基於標記——清除演算法實現的,所以會產生一些碎片空間,當碎片空間不足以放下物件的時候,就會觸發 Full GC

產品大大:那麼它現在這麼樣了

我:它啊,在 JDK 9 中被標記為Deprecate,而在最新更新的 JDK 14 中徹底的退出了歷史的舞臺,此情可待成追憶,只是當時已惘然啊~

image-20200326223034422

我:下面我來介紹一款當下我們在用的收集器吧,也就是鼎鼎大名的 G1 收集器 ~

G1 收集器

我:Garbage First 也稱 G1,冷酷的外表,無情的效能,超前的設計,簡直就是一個木得感情的殺手(biu~

產品大大:說人話(手動微笑臉

我: G1 收集器是又一個里程碑意義的收集器,它開創了收集器面向區域性收集的設計思路和基於Region的記憶體佈局方式。

產品大大:此話怎解?

我:在我們之前所瞭解的收集器中,我們都是基於記憶體分代理論,把記憶體區域分為老年代和新生代,不同區域的採用不同的收集器去完成,而G1顛覆了這個概念,它不再去區分所謂的老年代和新生代,而是面向全堆進行收集,把 Java 堆劃分為多個大小相等的獨立區域,在進行垃圾回收的時候,會將需要回收的區域組合成回收集(Collection Set),哪塊兒記憶體存放的垃圾數量最多,回收收益最大,優先去回收那些收益最大的區域,這也是Garbage First 這個名字的由來~

產品大大:那麼我有一個疑問,如果物件的大小過於龐大,一個區域無法放下這個物件的時候該怎麼辦呢?

我:首先說明一下,雖然化整為零,但是仍然儲存了老年代和新生代的概念,只不過這一塊兒不是一個固定的區域了,而是一系列區域的動態集合,而這些大物件會被存放在 N 個連續的 Humongous Region 中,這些 Region 被視為老年代的一部分~

產品大大:那麼和CMS相比起來,它的優勢在於什麼地方呢?

我:相對於CMS來說,最根本的一點,CMS是基於「標記——清除」演算法實現的,而 G1 是通過「標記——整理」演算法,每個區域之間又是通過「標記——複製」演算法,這樣的做法不會產生大量的記憶體碎片,可以使程式更加長時間的穩定執行,省的因為出現大物件無法分配而去進行下一次 Full GC。

產品大大:那麼 G1 的缺點其實也很明顯,它的功能很強大,帶來的是對記憶體的負荷和對CPU的壓力,畢竟世界上沒有免費的午餐~

我:沒錯呢,因為在處理跨 Region 引用的時候使用的卡表技術,標記——複製演算法這些都會導致它的記憶體壓力要比CMS高20%,但是不論是對於它所帶來的提升和紅利來說,還是對於一代新人換舊人的歷史發展來說,我們無疑是更傾向於去選擇 G1 的~

產品大大:嗯吶,下面還有嘛~

我:下面簡單的介紹兩款實驗過程中的收集器吧,雖然不知道未來它們會怎麼樣,但是它們就像當年的 G1 的一樣,未來可期~

產品大大:好的呢~

Shenandoah 收集器 && ZGC 收集器

我:下面簡單介紹一下這兩款低延遲收集器,在我們的收集器中,一般主要評判的標準有三種:記憶體佔用。吞吐量。延遲,在這三者之中,我們最多可以在兩個之間做到極致,隨著我們計算機硬體的發展,記憶體佔用慢慢沒有那麼重要,人們越來越看重延遲,所以低延遲就稱為我們追求的目標,而這兩款收集器就是為了達到GC只需 10ms 的目標去的~

產品大大:那麼它們到底是什麼呢?

我:一個叫做Shenandoah收集器,一個叫做ZGC收集器,它們兩個功能非常相似,只不過 ZGC 是親生的,而Shenandoah是抱養的,它們都可以看作是 G1 的繼承者,G1 在一定程度上也對這兩個程式碼有所借鑑和改進。

我:ZGC 收集器是一款基於 Region 記憶體佈局的,(暫時)不設分代的,使用了讀屏障,染色指標和記憶體多重對映等技術來實現可併發的標記——整理演算法的,以低延遲為首要目標的一款垃圾收集器。它的技術實現,我這裡就不再詳細去講了,等到它應用的比較廣泛的時候,再去研究也不晚~

產品大大:行吧~ 那我們今天到這裡,下次我們繼續,我們現在出去擼串~