一篇很不錯的垃圾回收機制的文章

轉載自:https://www.cnblogs.com/li573925122/articles/8568416.html

Java的內存分佈

在JVM中,內存是按照分代進行組織的。

 

 

其中,堆內存分爲年輕代和年老代,非堆內存主要是Permanent區域,主要用於存儲一些類的元數據,常量池等信息。而年輕代又分爲兩種,一種是Eden區域,另外一種是兩個大小對等的Survivor區域。之所以將Java內存按照分代進行組織,主要是基於這樣一個“弱假設” - 大多數對象都在年輕時候死亡。同時,將內存按照分代進行組織,使得我們可以在不同的分代上使用不同的垃圾回收算法,使得整個內存的垃圾回收更加有效。

年輕代的垃圾回收

在年輕代上採用的垃圾回收算法是“Mark-Copy”算法,並不同於我們前面所瞭解的任何一種基本垃圾回收算法,但是Mark算法是一樣的,基於根對象找到所有的可達對象,具體可看Mark-Sweep算法中的Mark步驟. 而對於Copy算法,它僅僅是簡單的將符合一定年齡的對象從一個分代拷貝到另一個分代。具體的回收過程如下:

 

首先,新對象的內存分配都是先在Eden區域中進行的,當Eden區域的空間不足於分配新對象時,就會觸發年輕代上的垃圾回收(發生在Eden和Survivor內存區域上),我們稱之爲"minor garbage collection".同時,每個對象都有一個“年齡”,這個年齡實際上指的就是該對象經歷過的minor gc的次數。如圖1所示,當對象剛分配到Eden區域時,對象的年齡爲“0”,當minor gc被觸發後,所有存活的對象(仍然可達對象)會被拷貝到其中一個Survivor區域,同時年齡增長爲“1”。並清除整個Eden內存區域中的非可達對象。

當第二次minor gc被觸發時(如圖2所示),JVM會通過Mark算法找出所有在Eden內存區域和Survivor1內存區域存活的對象,並將他們拷貝到新的Survivor2內存區域(這也就是爲什麼需要兩個大小一樣的Survivor區域的原因),同時對象的年齡加1. 最後,清除所有在Eden內存區域和Survivor1內存區域的非可達對象。

當對象的年齡足夠大(這個年齡可以通過JVM參數進行指定,這裏假定是2),當minor gc再次發生時,它會從Survivor內存區域中升級到年老代中,如圖3所示。

其實,即使對象的年齡不夠大,但是Survivor內存區域中沒有足夠的空間來容納從Eden升級過來的對象時,也會有部分對象直接升級到Tenured內存區域中。

年老代的垃圾回收

當minor gc發生時,又有對象從Survivor區域升級到Tenured區域,但是Tenured區域已經沒有空間容納新的對象了,那麼這個時候就會觸發年老代上的垃圾回收,我們稱之爲"major garbage collection".

而在年老代上選擇的垃圾回收算法則取決於JVM上採用的是什麼垃圾回收器。通過的垃圾回收器有兩種:Parallel Scavenge(PS) 和Concurrent Mark Sweep(CMS)。這兩種垃圾回收器的不同更多的是體現在年老代的垃圾回收過程中,年輕代的垃圾回收過程在這兩種垃圾回收器中基本上是一致的。

就像其名字所表示的那樣,Parallel Scavenge垃圾回收器在執行垃圾回收時使用了多線程來一起進行垃圾回收,這樣可以提高垃圾回收的效率。而Concurrent Mark Sweep垃圾回收器在進行垃圾回收時,應用程序可以同時運行。

Parallel Scavenge

PS垃圾回收器在年老代上採用的垃圾回收算法可以看作是標記-清除算法標記-壓縮算法的結合體。

首先,PS垃圾回收器先是會在年老代上使用標記-清除算法來回收掉非可達對象所佔有的空間,但是我們知道,標記清除算法的一個缺陷就是它會引起內存碎片問題。繼而有可能會引發連續的major gc。假設當前存在的內存碎片有10M,但最大的內存碎片只能容納2M的對象,這個時候如果有一個3M的對象從Survivor區域升級到Tenured區域,那Tenured區域也沒有辦法存放這個3M的對象。結果就是不斷的觸發major gc,直到Out of Memory。所以,PS垃圾回收器在清除非可達對象後,還會進行一次compact,來消除內存碎片。

 

Concurrent Mark Sweep

CMS垃圾收集器相比於PS垃圾收集器,它成功的減少了垃圾收集時暫停應用程序的時間,因爲CMS在進行垃圾收集時,應用程序是可以並行運行的。下面讓我們來看看它是怎麼做到的。

從它的名字可以看出,CMS垃圾收集器在年老代上採用的垃圾回收算法是標記-清除算法。但是,它跟標準的標記-清除算法略有不同。它主要分爲四個階段:

  1. Initial Mark階段 - 這個階段是Stop-The-World的,它會暫停應用程序的運行,但是在這裏階段,它不會標記出在Tenured區域中所有的可達對象。它只會從根對象開始出發,標記到根對象的第一層孩子節點即停止。然後恢復應用程序的運行。所以,這個暫停應用程序的時間是很短的。
  2. Concurrent Mark階段 - 在這個階段中,CMS垃圾回收器以Initial Mark階段標記的節點爲根對象,重新開始標記Tenured區域中的可達對象。當然,在這個階段中是不需要暫停應用程序的。這也是它稱爲"Concurrent Mark"的原因。這同時也造成了一個問題,那就是由於CMS垃圾回收器和應用程序同時運行,Concurrent Mark階段它並不保證在Tenured區域的可達對象都被標記了 - 應用程序一直在分配新對象。
  3. Remark階段 - 由於Concurrent Mark階段它並不保證在Tenured區域的可達對象都被標記了,所以我們需要再次暫停應用程序,確保所有的可達對象都被標記。爲了加快速度,這裏也採用了多線程來同時標記可達對象。
  4. Concurrent Sweep階段 - 最後,恢復應用程序的執行,同時CMS執行sweep,來清除所有非可達對象所佔用的內存空間。

從下圖可以看到PS和CMS垃圾收集器的區別:

 

 

黑色箭頭代表應用程序的運行,綠色箭頭代表CMS垃圾收集器的運行。一根線條表示單線程,多個線條表示多線程。

所以,相比於PS垃圾收集器,CMS垃圾收集器成功的減少了應用程序暫時的時間。

Garbage First(G1)垃圾收集器

但是很不幸的是,CMS垃圾收集器雖然減少了暫停應用程序的運行時間,但是由於它沒有Compact階段,它還是存在着內存碎片問題。於是,爲了去除內存碎片問題,同時又保留CMS垃圾收集器低暫停時間的優點,JAVA7發佈了一個新的垃圾收集器 - G1垃圾收集器。它會在未來逐步替換掉CMS垃圾收集器。

G1垃圾收集器和CMS垃圾收集器有幾點不同。首先,最大的不同是內存的組織方式變了。Eden,Survivor和Tenured等內存區域不再是連續的了,而是變成了一個個大小一樣的region - 每個region從1M到32M不等。

 

一個region有可能屬於Eden,Survivor或者Tenured內存區域。圖中的E表示該region屬於Eden內存區域,S表示屬於Survivor內存區域,T表示屬於Tenured內存區域。圖中空白的表示未使用的內存空間。G1垃圾收集器還增加了一種新的內存區域,叫做Humongous內存區域,如圖中的H塊。這種內存區域主要用於存儲大對象-即大小超過一個region大小的50%的對象。

在G1垃圾收集器中,年輕代的垃圾回收過程跟PS垃圾收集器和CMS垃圾收集器差不多,新對象的分配還是在Eden region中,當所有Eden region的大小超過某個值時,觸發minor gc,回收Eden region和Survivor region上的非可達對象,同時升級存活的可達對象到對應的Survivor region和Tenured region上。對象從Survivor region升級到Tenured region依然是取決於對象的年齡。

 

 

對於年老代上的垃圾收集,G1垃圾收集器也分爲4個階段,基本跟CMS垃圾收集器一樣,但略有不同:

  1. Initial Mark階段 - 同CMS垃圾收集器的Initial Mark階段一樣,G1也需要暫停應用程序的執行,它會標記從根對象出發,在根對象的第一層孩子節點中標記所有可達的對象。但是G1的垃圾收集器的Initial Mark階段是跟minor gc一同發生的。也就是說,在G1中,你不用像在CMS那樣,單獨暫停應用程序的執行來運行Initial Mark階段,而是在G1觸發minor gc的時候一併將年老代上的Initial Mark給做了。
  2. Concurrent Mark階段 - 在這個階段G1做的事情跟CMS一樣。但G1同時還多做了一件事情,那就是,如果在Concurrent Mark階段中,發現哪些Tenured region中對象的存活率很小或者基本沒有對象存活,那麼G1就會在這個階段將其回收掉,而不用等到後面的clean up階段。這也是Garbage First名字的由來。同時,在該階段,G1會計算每個 region的對象存活率,方便後面的clean up階段使用 。
  3. Remark階段 - 在這個階段G1做的事情跟CMS一樣, 但是採用的算法不同,G1採用一種叫做SATB(snapshot-at-the-begining)的算法能夠在Remark階段更快的標記可達對象。
  4. Clean up/Copy階段 - 在G1中,沒有CMS中對應的Sweep階段。相反 它有一個Clean up/Copy階段,在這個階段中,G1會挑選出那些對象存活率低的region進行回收,這個階段也是和minor gc一同發生的,如下圖所示:

     

從上可以看到,由於Initial Mark階段Clean up/Copy階段都是跟minor gc同時發生的,相比於CMS,G1暫停應用程序的時間更少,從而提高了垃圾回收的效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章