1. 程式人生 > >jvm垃圾收集機制詳解(中)

jvm垃圾收集機制詳解(中)

上一篇傳送門:jvm垃圾收集機制詳解(上)

二、垃圾收集演算法(僅演算法思想)

1.標記清除演算法

標記清除演算法是另外兩種垃圾回收演算法的基礎,之所以說是基礎是因為這種演算法僅僅是簡簡單單地把標記了需要清除的物件進行了回收而已,除此之外沒有任何其它操作。這種演算法有很多不足,例如標記和清除的效率不高,清楚之後明明空閒記憶體的總和足夠,但是就是無法容納下大物件從而必須提前觸發下一次垃圾回收或者乾脆就報記憶體溢位異常了。

下面是這種演算法的示意圖:

  • 綠色:存活的物件
  • 黑色:要被回收的物件
  • 白色:剩餘的空閒記憶體空間

垃圾回收前:
在這裡插入圖片描述
垃圾回收後:
在這裡插入圖片描述

2.複製演算法

為了解決標記清除演算法的問題,複製演算法應運而生,這種演算法的思想是把記憶體分成兩塊,我們先使用其中的一塊,當使用的這一塊需要進行垃圾回收的時候,就把這塊上面還存活著的物件複製到另一塊上面,然後把原來的這塊上面的所有物件都回收掉。這種演算法的代價是浪費了一半的記憶體。

圖示:

回收前:
在這裡插入圖片描述
複製物件:
在這裡插入圖片描述
回收左半區所有物件:
在這裡插入圖片描述
回收後:
在這裡插入圖片描述

由於這種演算法浪費了一半的記憶體,因此我們現在的垃圾回收機制做了一些改進,我們用這種演算法去回收新生代物件,新生代物件在記憶體中存活的時間往往很短,IBM公司的研究表名,新生代中幾乎98%的物件都很“短命”,也就是說活著的新生代物件較少,死去的新生代較多,也就是說我們每次複製的物件並不多,佔用的空間並不大。那麼,我們幹嘛還要1:1地去劃分兩塊記憶體呢?

於是,現在的虛擬機器都不去等大小地去劃分兩塊記憶體,而是劃分三塊記憶體,一塊大的,兩塊小的,我們每次使用那塊大的和其中一塊小的,當需要使用複製演算法回收物件的時候,就把大的和小的中存活的物件一起復制到未使用的小塊中,然後一併回收大塊和原來使用的小塊中的物件,接下來繼續使用一塊大的和我們最後複製到的小塊,以此類推。這裡的大塊叫做Eden,兩個小塊叫做Survivor。以我們現在最常用的HotSpot虛擬機器為例,劃分一大兩小的比例是8:1:1,如下圖所示:
在這裡插入圖片描述
垃圾回收過程圖示:
回收前:
在這裡插入圖片描述
複製到Survivor2:
在這裡插入圖片描述
回收Eden和Survivor1區所有物件:
在這裡插入圖片描述
回收後:
在這裡插入圖片描述
接下來繼續使用Eden區和Survivor2區,再需要回收時就該輪到向Survivor1區複製了

使用這種演算法有一個問題,就是我們沒法保證每次Eden區和一個Survivor區中存活的物件加起來一定小於等於我們總儲存空間的10%,也就是一定小於等於另一個Survivor區的大小,那麼要是該複製的時候發現另一個Survivor區裝不下了怎麼辦?

這就需要我們有一個可以擔保的空間,就好比我們去銀行貸款需要擔保人或者抵押房產之類的一樣,當Survivor空間不夠用的時候,我們需要依靠老年代進行分配擔保。

另外注意這種演算法的使用場景,一定要在存活的需要複製的物件比較少的情況使用,否則複製大量的物件也是很大的一筆開銷,所以這就解釋了為什麼我們用這種演算法去回收新生代物件。

3.標記整理演算法

複製演算法在存活物件較多的時候不太適合,原因上面說過了,所以老年代物件的回收不適合使用這種演算法,根據老年代的特點,我們選擇了“標記整理”演算法進行回收。標記過程沒什麼可說的,而後續的步驟時將存活的物件都向一側去移動,覆蓋掉前面的將被回收物件,然後直接清理掉另一側剩餘的所有被回收物件。演算法圖示如下:

回收前:
在這裡插入圖片描述
將存活物件向一側移動,如果遇到將被回收的物件,就覆蓋它:
在這裡插入圖片描述
回收後:
在這裡插入圖片描述

下一篇傳送門:jvm垃圾收集機制詳解(下)

  • 參考書籍:《深入理解Java虛擬機器》