1. 程式人生 > 程式設計 >Jvm記憶體回收終極奧義:垃圾收集器

Jvm記憶體回收終極奧義:垃圾收集器

前言:

之前在一個java物件的死亡證明我們講了一個物件是如何被判定為死亡的,並在之後的 Jvm記憶體回收終極奧義:垃圾收集演演算法裡詳細地介紹了目前較為流行的四種垃圾回收演演算法,後來各大虛擬機器器充分驗證了這四種演演算法的可行性,所謂有需求就會有市場,本著回收垃圾,掌握核心科技的強大信念,垃圾公司應運而生了(你確定不是在罵人?),垃圾公司一口氣推出了覆蓋各大場景的七種垃圾收集器供廣大消費者選擇,不費吹灰之力佔領了百分之90的市場份額,公司成立一天,火速上市,而競爭者們則被安排去了非洲,故事到這裡,JVM宇宙正式形成了。

是的,一本正經的胡說八道。

這七種產品分別為:

  • 單執行緒垃圾收集小王子:Serial收集器
  • 多執行緒垃圾收集小王子:ParNew 收集器
  • 吞吐量優先小王子:Parallel Scavenge收集器
  • 單執行緒垃圾收集老王子 :Serial Old收集器
  • 多執行緒垃圾收集老王子:ParNew Old 收集器
  • 垃圾收集器明日之星:CMS收集器
  • 來自未來的次世代收集器:G1收集器

憑著在垃圾回收領域的優秀表現,無數次拯救了記憶體危機,它們被後人稱為垃圾回收者聯盟

Serial收集器:

首先第一個上場的是Serial收集器,它是所有收集器裡面年齡最大的收集器,在jdk1.3之前那物質貧瘠的時代,Serial收集器就像IE一樣是唯一的選擇,Serial的垃圾收集方式有點類似於我們平常的保潔阿姨,收集垃圾的時候,會先告訴在場的所有執行緒:

你,你,你,別動,剛掃完

Serial收集器工作的時候會暫停掉其他工作執行緒,直到它收集結束,這對很多應用是難以接受的,比如你再打一款遊戲java榮耀,正準備團戰呢,Serial收集器站出來說,弟弟們先往邊上靠靠,我要來收集垃圾了,給你暫停個五分鐘,誰受得了,下面是Serial收集器的執行過程,其中新生代使用的是複製演演算法,老生代使用標記-整理演演算法

讀者看到這裡肯定會說了,這垃圾收集器也太垃圾了吧,其實不然,Serial的專一(單執行緒)收集在只有一個核心的系統中因為不需要去和其他回收執行緒進行互動,反而效率更高一點。

ParNew 收集器:

ParNew 收集器呢,其實就是Serial收集器的多執行緒版本,其中用到的收集演演算法,Stop the word(停頓型別STW,暫停其他執行緒),物件分配規則,回收策略幾乎都與Serial收集器一模一樣,但是青出於藍而勝於藍,ParNew在實行垃圾回收的時候是採用多執行緒的,當然,這並不足以使它立足於java虛擬機器器世界的理由,它最大的優勢就是和CMS關係特好,幾乎是拜把子的兄弟,倆人合作起來默契十足,而這些,Serial 和 Parallel Scavenge收集器就只能相形見絀了,可見,一個好的隊友是多麼重要。ParNew的工作過程如下圖所示:

你這圖看著有點斜啊,大家不要在意這些細節

但是parNew收集器並不是說因為加了多執行緒就完爆Serial收集器了,當只有一個核心的時候,ParNew的效率就通常沒有Serial收集器高,原因在於一個小房間通常一個人打掃起來比十個人掃起來快,因為沒有互相溝通的成本,但隨著核心個數的增加,ParNew的優勢才會逐漸體現出來。

Parallel Scavenge收集器:

巧了,Parallel Scavenge是一個新生代收集器,和ParNew收集器在多執行緒,複製演演算法又幾乎差不多,那他究竟在厲害在哪了,沒錯,是Idea,當別的垃圾收集器都在拼命減少使用者執行緒停頓時間的時候,它另闢蹊徑,選擇了吞吐量優先。

PS:吞吐量 = 使用者執行程式碼時間/(使用者執行程式碼時間+垃圾收集器時間)

吞吐量越高意味著使用者程式碼執行的時間就越高,這裡引用深入理解jvm虛擬機器器中的原文,感覺我怎麼說也沒作者說得好。

停頓時間越短越適合需要與使用者互動的程式,良好的相應速度能提升使用者體驗,而高吞吐量則可以高效的利用CPU時間,儘快完成程式的運算任務,主要適合在後臺運算而不需要太多互動的任務。

存在感有點低,這個垃圾收集器沒有圖,,,

Serial Old收集器:

Serial Old收集器是Serial收集器的老年代版本,它同樣是單執行緒的收集器,使用標記-整理演演算法,它主要工作在Client模式,由於Parallel Scavenge收集器和Serial和cms收集器關係都不太好,所有它們兩個成了好朋友,一個新生代,一個老年代,所以可以配合使用,第二個是作為CMS收集器的後備預案。它的工作圖如下所示:

ParNew Old 收集器:

同樣的,ParNew Old收集器則是ParNew收集器的老年代版本了,使用多執行緒標記-整理演演算法,由於上文我們說到,Parallel Scavenge收集器和CMS收集器尤其不合,所以如果一旦選擇了Parallel Scavenge收集器作為新生代收集器的話,那也就意味著老年代收集器我們只能選Serial Old收集器,而Serial Old收集器屬於單執行緒處理器,難免會有著效能上的問題,所以這就比較蛋疼了,簡直就是捆綁銷售,這一切直到ParNew Old 收集器的面世才得到解決,至此,Parallel Scavenge收集器+ParNew Old 收集器才算是真正的實現了吞吐量優先的至臻組合。

下圖是ParNew Old 收集器執行示意圖:

CMS收集器:

CMS收集器,全稱Concurrent Mark Sweep 收集器,和大多數收集器類似,也是致力於縮短執行緒停頓時間的垃圾收集器。本身採用的是進階版的標記-清除演演算法

主要分為四個流程:

  1. 初始標記
  2. 併發標記
  3. 重新標記
  4. 併發清除

其中併發標記和併發清除這兩個做真正工作的佔據了整個垃圾回收過程的絕大多數時間。初始標記可以說是先來初略的看一眼誰需要標記,花不了多少時間,重新標記則是看看哪個被標記的改動了,也是很快就完成的。

執行流程如下:

雖然CMS收集器是今日之星收集器,但是也並非完全沒有缺陷,其中有三個比較明顯的問題。

  • 第一個就是CMS收集器使用的標記-清除演演算法會導致大量的空間碎片,原因我已經在上篇文章垃圾收集演演算法有說明,不瞭解的朋友可以去看一下。
  • 第二個則是對CPU資源非常敏感:大家想,流程圖那麼多執行緒,當CPU多的時候還能接受,我家底厚,你吃得多也差不多能養起,可是當CPU數量比較少的時候,就相當於一個母親養一堆嗷嗷待哺的執行緒孩子,就會很吃力,相應的也會導致我們應用效能的下降。
  • 第三個就是沒辦法回收“浮動垃圾”:這個浮動垃圾是什麼意思呢,大家這麼理解,你在家掃地呢,你弟弟在旁邊嗑瓜子,當你掃到你弟弟那邊的時候,你邊掃它邊往你掃乾淨的地方仍,但是,虛擬機器器規定你一次只能掃一次,沒法回去,所以你得等下一次掃的時候在處理這些垃圾。所以,CMS無法回收哪些執行緒在回收的過程中因為執行而產生的新垃圾,也就是浮動垃圾。

G1收集器:

G1收集器我稱它為次世代垃圾收集器,一款來自未來的垃圾收集器,真實的講,就是G1是當今垃圾收集器發展的最前沿的成果之一,但是,由於一些問題沒有解決所以遲遲不能上市,直到JDK7才正式加入jvm虛擬機器器大家庭,JDK9才作為預設的垃圾回收器出現。

G1的出現不是工程師們覺得垃圾回收器太少再隨便加個,根據我們以往的歷史經驗,一款新技術的誕生必定是為了彌補舊技術的不足,G1同樣是為了彌補CMS垃圾收集器的一些問題而誕生的,相對於CMS垃圾回收器,G1的主要提升主要在如下幾個方面:

  • G1在壓縮空間方面有優勢
  • G1通過將記憶體空間分成區域(Region)的方式避免記憶體碎片問題 Eden,Survivor,Old區不再固定、在記憶體使用效率上來說更靈活
  • G1可以通過設定預期停頓時間(Pause Time)來控制垃圾收集時間避免應用雪崩現象
  • G1在回收記憶體後會馬上同時做合併空閒記憶體的工作、而CMS預設是在STW(stop the world)的時候做
  • G1會在Young GC中使用、而CMS只能在O區使用

而相較於其他的垃圾收集器而言,G1主要有如下四個特點:

  • 並行與併發:能更好的利用多CPU的優勢。
  • 分代收集:這個就厲害了,強大到沒隊友,可以管理整個GC堆,一個人carry全記憶體。
  • 空間整合:薛定諤的垃圾回收演演算法,整體看是基於標記-整理演演算法實現的,區域性看又像是基於複製演演算法實現的,但無論使用它們兩個中的哪個,都幾乎不會產生空間記憶體碎片。
  • 可預測的停頓:有點像AI,會建立可預測的停頓時間模型,可以有計劃的避免在整個Java堆實現全區域的垃圾收集。

而G1是實現流程相對來說還是很複雜的,在這裡我們就不多加敘述了,想要了解的同學可以去搜索相關資料。

和CMS相同,G1的垃圾回收過程也主要分為四個階段:

  1. 初始標記
  2. 併發標記
  3. 最終標記
  4. 篩選回收

流程圖如下:

總結:

本篇文章我們較為初略的說了說jvm七種垃圾收集器,各有所長,沒有絕對的吊打,只有場景的選擇。合適的場景用合適的垃圾收集器,才會使我們的應用效能得到改善。

我是韓數,關注我,有你好果子吃(哼)

點個贊在走哦。

等一下:

相關原始碼歡迎去我的github下載(歡迎star):

github.com/hanshuaikan…