1. 程式人生 > >「給產品經理講JVM」:垃圾收集演算法

「給產品經理講JVM」:垃圾收集演算法

糾結的我,給我的JVM系列終於起了第三個名字,害,我真是太難了。從 JVM 到 每日五分鐘,玩轉 JVM 再到現在的給產品經理講 JVM ,雖然內容為王,但是標題可以讓更多的人看到我的文章,所以,歷經了三個選題,最終定下來了這個。

這個名字的由來,且聽我給你慢慢道來,從學習知識的角度上來說,最深入的方法就是把知識講給別人聽,那麼為什麼我要講給程式設計師的天敵——產品經理呢?

那麼問題的答案很簡單,因為我「老婆」就是一個「產品經理」哈哈哈哈哈哈哈(聽說現在流行這樣玩?

認識的時候她是 Java 開發來著,誰知道領了證就變成了產品。。小 朋 友 你 是 否 有 很 多 ???(手動攤手

廢話不多說了,下面開始進入我們的正文~

背景

故事發生在一個明媚的午後,我剛看完《深入理解Java虛擬機器》第三章的第三節——垃圾收集演算法,我的產品大人開始了例行盤問

產品大大:你天天學的什麼啊,我看著好像很眼熟??

我:???你難道忘了你曾經是一個 Java 程式設計師,JVM 都忘了?

產品大大:JVM 啊,基本上忘的差不多了,要不你給我講講,省的我們「開發天天忽悠我」

於是,我就這麼做了程式設計師的叛徒

我:行吧,那我就來給你講一講這一節中我的收穫吧。

接上集

我:在開始之前,我們先來回顧一下上次講的東西,上次我們說到了如何判定一個物件是不是垃圾物件(已死),通常來說有兩種演算法——「引用計數法和可達性分析法」,目前市面上的虛擬機器大多數採用的是第二種——可達性分析法。

產品大大:那麼這兩種方法是不是對應了兩個型別的垃圾收集演算法呢?

我:真聰明,果然「不是一家人,不進一家門」,這兩種分別對應了Reference Counting GC(引用計數垃圾收集)和 Tracing GC(追蹤垃圾收集),而我們今天講的垃圾收集演算法都屬於追蹤垃圾收集的範疇。不過在介紹垃圾收集演算法之前,我首先需要向你介紹一個很關鍵的理論——分代收集理論。

產品大大:好嘞

分代收集理論

我:所謂分代收集理論從某種意義上來說,是一種約定俗稱的規範和經驗的總結,並不是一個非常嚴格的規則。

產品大大:嗯嗯,約定大於配置,現在這樣的思想在很多地方都有體現,那麼到底什麼是分代收集呢?

我:且聽我慢慢道來,分代收集建立在三個假說之上:

  1. 絕大多數物件都是朝生夕死的。
  2. 熬過越多次的GC的物件越難以消亡。
  3. 跨代飲用相對於同代引用僅佔極少一部分。

產品大大:這三個我好像在哪見過,但是記不太清楚了,能不能說的更詳細一點

我:由於絕大多數物件都是朝生夕死的,而熬過越多次GC的物件越難消滅掉,這樣自然而然的就會把物件分成兩個派別,一種是極易發生GC的,一種是極難發生GC的,極易發生GC的生命週期較短,所以也被稱之為「新生代」,極難發生GC的物件生命週期較長,所以也可以叫他們「老年代」。

產品大大:這麼一說,我好像有點明白了,但是為什麼要分代呢?

我:如果我們把他們進行分代後,可以對區域進行劃分,一部分用於儲存新生代,新生代的物件我們只需要去關注那些不被回收的物件就可以,而不用去標註絕大多數需要回收的物件;一部分用於儲存老年代,老年代發生GC的頻率較低。這兩個區域的GC頻率是不同的,所以分開進行GC的話可以節省很多時間和儲存這些物件的空間。

產品大大:原來是這樣,那麼為什麼會有第三個假說呢?

我:如果出現新生代中存在老年代物件的引用,或者老年代物件中存在新生代物件的引用,這樣的現象被我們稱之為跨代引用,而我們為了確保可達性分析法的準確性,還需要去遍歷老年代中的物件,這樣就會造成很大的效能壓力和負擔。這時第三條結論就應運而生,為什麼會有這條結論呢?你想一想,如果一個新生代物件在進行了幾次GC後,因為跨代引用的原因,仍然沒有被回收掉,那麼這個新生代物件就會晉升到老年代中。於是就解決了這個跨代的問題。

產品大大:那麼第三個假設對應的措施是什麼呢?

我:文字未免太過蒼白,這個適合用圖來表達,看好咯

我:這樣的話,會不會很清晰(得意臉

產品大大:明白了,那我們繼續吧,是不是要進入正題了

我:嗯哼,下面我就來介紹最基礎的一種演算法——標記清除演算法

標記-清除

產品大大:這個我知道,就像它名字那樣,標記,然後清除,見名知意嘛哈哈哈哈。

我:厲害了,不愧是技術出身的產品,佩服佩服,確實像你所說的那樣,就很簡單的兩步,標記——清除。

我:但是這樣的方式由於過於簡陋,雖然在物件較少的時候,效率比較快,但是當物件一多起來的話,標記和清除的效率都會有所下降,而且這樣的方式會造成一個問題——產生記憶體碎片,如果剩下的空白格無法放下一個較大的物件時,就會提前觸發另外一個GC。

產品大大:說到這裡,好像還漏了點東西,GC針對不同的分代是不是有不同的GC叫法來著?

我:對的,我疏忽了,針對新生代的GC一般稱之為(Young GC),針對老年代的GC一般稱之為(Old GC),如果是堆和方法區全部回收的話,則被稱之為Full GC。

產品大大:這樣倒也算好記,OK,我們來進入下一個演算法吧。

我:下一個演算法,可以看作是第一個演算法的升級版本,叫做「標記——複製演算法」。

標記-複製

我:標記複製演算法,最開始形態是半區複製,就是把之前的內容按照容量劃分成等量的兩部分,一部分用完之後,就把剩餘存活的物件丟到另一半,然後把這一半進行清除,變成預留的另一塊。如果是絕大多數物件需要回收(例如新生代)的情況下,這樣的方法就會很便捷高效,但是這是一種犧牲空間來換取時間的做法,我們僅僅有效利用了一半的空間,有一半的空間被浪費掉了。

產品大大:所以後來一定會有所改進的,對嗎?(期盼臉

我:沒錯,新的複製演算法不能稱之為半區複製了,因為新生代記憶體範圍被分割成了三個部分,一個Eden區和兩個Survivor區,他們之間的比例是8:1:1。在進行GC的時候,使用一個Eden和一個Survivor區,然後把剩餘的存活物件放到另一個Survior區域中,清理掉剩下的區域,就可以完成一次收集的過程,這樣僅僅有百分之十的空間被浪費,相對於優點來說,這樣的缺點,我們是完全可以接受的,

產品大大:那如果剩下的Survivor不足以放下剩下存活的物件該怎麼辦呢?

我:問的好,這個時候就要談到一個新的概念——「記憶體擔保」了,如果不夠,就會去找老年代,如果老年代的「連續空間」大於新生代物件總大小或歷次晉升的平均值,就可以借給Survivor區使用,至於為什麼是這樣,且待我賣個關子(其實是我也不知道哈哈哈哈哈

產品大大:那麼這種演算法聽起來好像沒有什麼缺點的亞子?

我:並不然,複製的操作會降低迴收的效率,而且,如果是老年代進行回收的話,你向誰去借呢?所以又出現了第三種演算法——標記整理演算法。

標記-整理

產品大大:那麼什麼是標記整理呢?

我:標記-整理演算法是一種移動型的演算法,通過將存活物件向一邊移動,然後直接清除掉邊界外的記憶體以達到不會產生記憶體碎片的回收演算法。

產品大大:那麼這個演算法的優點就是在於它不會產生記憶體碎片,減少了回收的次數,那麼它和標記-整理相比孰優孰劣呢?

我:對的,雖然在它移動物件的時候會花費不少的時間和資源,但是相對於標記-清除演算法來說,頻繁的GC所消耗的時間會更多,當然在實際的使用過程中,這三種演算法會在不同的應用場景下結合去使用,比如先使用標記-清除演算法,當記憶體碎片達到一定程度後,再使用標記-整理演算法去清除,不同的組合會有不同的效果,具體什麼效果,我會在給你介紹一些常見的垃圾收集器時進行講解(下一節喲

產品大大:好的~ 那今天就先到這裡吧,講的差不多夠我吸收個三五天的了,過兩天你再給我講吧~

我:好噠。

最後的最後

咳咳,如果知識無法讓你吃飽,那你可以多吃點狗糧哈哈哈哈哈哈(太皮會不會捱打嘻嘻

晒一張產品大大給我的備註。

下面又是「精彩」的恰