面試官:談談你對JVM垃圾收集器演算法的瞭解

yes的練級手冊發表於2019-04-27

本文預設JVM為HotSpot,在介紹各款垃圾收集器之前先介紹下垃圾收集器的演算法,不是講解演算法如何實現,而是介紹下演算法的處理思想和一些優缺點。

垃圾收集器演算法

垃圾收集器用於清除垃圾的演算法有:標記-清除演算法、複製演算法、標記-整理演算法。 用於針對物件不同的存活週期而分代的演算法有:分代收集演算法。這個演算法把Java堆分為新生代和老年代。

標記-清除演算法(Mark-Sweep)

標記-清除演算法應該是最符合我們人一開始處理垃圾的思路的演算法,例如我們想清除房間的垃圾,我們肯定是先定位(對應標記)哪些是垃圾,然後把這些垃圾之後扔了(對應清除),簡單粗暴,剩下的不是垃圾的東西我也懶得理,不管了哈哈哈。

當然有的人說我打掃房間會先整理不是垃圾的東西然後把垃圾扔了...你走錯片場了請去標記-整理片場(勤勞的孩子)。

標記-清除演算法
這演算法有兩個缺點 1.標記和清除的效率不高,按這種思路是一個一個標記過去,並且掃描哪些是標記過得然後才清除了 2.空間碎片問題,看上圖整理後中間空了好多,這樣會使得比較大的物件要申請比較多的連續空間的時候申請不到,明明你空間還很足的。然後導致又一次GC。

複製演算法

複製演算法等於說根據標記-清除演算法的不足之處進行了改進。簡單的來說它把空間切成了兩半,一次我就用一半,一半滿了我就把活著的物件放在另一半按順序放,然後無腦的把剛才使用的那一半空間一次清理乾淨,然後保留著存活的那些物件的記憶體空間換上去使用。這樣就沒了標記-清除演算法的空間碎片問題。

複製演算法
虛擬機器基本上用這種演算法來回收新生代,但是切一半空間利用率太低了,一次就只能用一半。所以在HotSpot中是把這一塊空間分為3塊,一塊Eden,兩塊Survivor。

因為正常情況下新生代的大部分物件都是短命鬼,所以能活下來的不多,所以預設的空間劃分比例是8:1:1。用法就是每次只使用Eden和一塊Survivor,然後把活下來的物件都扔到另一塊Survivor。再清理Eden和之前的那塊Survivor。然後再把Eden和存放存活物件的那一塊Survivor用來迎接新的物件。就等於每次回收了之後都會對調一下兩個Survivor。

但是事情總有意外,萬一這波物件短命鬼較少,存活下來的很多,那一個Survivor放不下,所以還有個擔保機制,就像我們現實生活中的擔保人,你還不起了擔保人上!這個擔保人就是老年代,也就是Survivor放不下了就放老年代去。 那為什麼虛擬機器基本上用這種演算法來回收新生代呢?就是因為新生代的物件大部分存活時間不長,所以每次GC的時候複製的比較少,效率高啊,每次就複製一點點物件到Survivor。

那要是到老年代也就是一些老不死的物件那用複製效率就低了啊,首先8:1:1這種分法就不合適了,因為每次存活下來的物件會很多,1就放不下了,你可能就得"五五開"分了,那"五五開"之分也就算了,因為每次物件基本上都活著,所以每次複製等於複製一半空間的物件。效率低啊。

還有,新生代有老年代做擔保啊,多了的物件可以放到老年代,而老年代不行啊,沒有依靠了。所以就又有了下面的演算法。

標記-整理演算法(Mark-Compact)

標記-整理演算法的思路也是和標記-清除演算法一樣,先標記那些需要清除的物件,但是後續步驟不一樣,它是整理,對就是像上面說的那些清除房間垃圾每次都會整理的人一樣那麼勤勞。

標記-整理
每次會移動所有存活的物件,且按照記憶體地址次序依次排列,也就是把活著的物件都像一端移動,然後將末端記憶體地址以後的記憶體全部回收。所以用了它也就沒有空間碎片的問題了。

分代收集演算法

這演算法就是把Java堆分為新生代和老年代,這樣好根據每個代的物件存活時間特點上不同的收集演算法。 所以一般新老代就是用複製演算法。老年代用標記-清除或標記-整理演算法。


如有錯誤歡迎指正! 個人公眾號:yes的練級攻略

相關文章