大話Minor GC與Full GC分別在什麼時候發生?

Java堆分區

要理解Minor GC和Full GC之前要先了解Java堆的分區。如果對Java堆進行細分地話,又可以分爲新生代(包含Eden空間、From Survivor空間、ToSurvivor空間)和老年代。

在這裏插入圖片描述

注意:這樣要補充一點。在HotSpot虛擬機中默認Eden和From Survivor、ToSurvivor的大小比例是8:1:1。所以上圖爲了展示清楚,畫的不是很準確。

垃圾收集分類

在這裏插入圖片描述

我們採用擬人的手法,把整個Java堆比喻成一個大房子,虛擬機比喻成房子的主人小V,來看看垃圾收集是怎麼一回事。

還有上面三個空間的名字太長了,我們這樣約定一下:Eden空間——伊甸空間,From Survivor空間——from空間,To Survivor空間——to空間

Minor GC

小V每次買了好多東西(new了很多對象)都喜歡放在伊甸+from這個房間,“哼~~你管我,我就是這個決定的”。房子雖然是豪宅但是也耐不住小V更豪,伊甸+from房間總有裝滿的一天。但是他又想買東西了,這個時候就只能把一些對他不重要的東西丟掉(Minor GC),好騰出空間放新東西。

即:大多數情況下,對象在新生代的Eden區中分配(更準確是在Eden+From Surivivor區分配)。當Eden區沒有足夠空間進行分配時,虛擬機將進行一次Minor GC。

Full GC

進行Full GC的情況有多種。

1. 調用System.gc()

這一天小V的母親建議他收拾一下房間,但是對於收不收拾房間,小V顯示有自己的想法。

即:調用System.gc()方法只是建議垃圾收集器進行Full GC,而垃圾收集器進不進行收集是不確定的。

2. 大象

這一次小V買了一頭大象,這真是實名副其實的大對象。這頭大象對小V來說決對是一個讓他頭疼的事,主要有下面兩個理由。

一、即便伊甸+from房間還有很多空間,但是這頭大象一來,他很大可能就要開始收拾房間了(Minor GC);二、後期還有可能要把這頭大象搬到to房間或老年代去,簡直欲哭無淚啊!!!

那麼幹脆直接放到老年代啊,但是往老年代放也不一定放的下啊,這時會進行Full GC好騰出更多的空間存儲大象。

即:HotSpot虛擬機提供了-XX:PretenureSizeThreshold參數,指定大於該設置值的對象直接在老年代分配。但是老年代不一定放的下,這時需要進行Full GC好騰出更多的空間。

3. 分配擔保

小V收拾伊甸+from房間的方法是這樣的,如果發現了好多對他還有用的東西(就是這個對象還有用,更準確地描述爲按照可達性分析算法到GC roots是可達的)。

對於這些東西,他處理的方式是這樣的,to房間放得下就是放to房間,to房間放不下就放老年代房間。於是又是同樣的問題,老年代也不一定放的下啊。

那麼這就不僅僅是老年代放不放的下的問題了,因爲Minor GC後,to房間放不下的一定要往老年代放,所以能不能進行Minor GC都是問題。

對於這個問題不同年齡段的小V有不同的處理方式。六歲零24天之後,他是這麼做的,比較一下老年代房間剩下的空間和新生代房間所有物品所佔的空間,如果老年代大,沒啥說的,收拾起來;如果老年代小,Full GC。

即:JDK 6 Update 24之後的規則是這樣的,只要老年代的連續空間小於新生代對象總大小,就進行Full GC。至於在該版本之前的處理方式比較複雜,這裏不詳細說明。

4. CMS收集器Concurrent Mode Failure

身爲理工男的小V是個電子發燒友,而且人家很耐燒,對此他做了很多種幫他收拾垃圾的機器人——CMS智能垃圾收集機器人只是其中的一種。

CMS收集器的垃圾收集過程可以分爲四個步驟:
(1)初始標記
(2)併發標記
(3)重新標記
(4)併發清除

併發清除的意思是,清理垃圾和用戶線程是併發進行的,既然用戶線程正在進行就會產生新的對象,而且這些新產生的對象還完美逃避了本輪的標記即不會被回收。

如果這些對象要存入老年代,而老年代又空間不夠,此時就會出現“Concurrent Mode Failure”進而導致Full GC的產生。

5. 永久代空間不足

關於永久代只有Sun/Oracle JDK和Open JDK中默認使用的HotSpot虛擬機中才有這個概念,其他虛擬機比如J9、JRockit中是沒有的。

在JDK8之前,當時的HotSpot虛擬機設計團隊,不但在Java堆的垃圾收集上使用了“分代設計”思想,還將其擴展到了方法區即用永久代來實現方法區,這樣HotSpot中的垃圾收集器就能像管理Java堆一樣管理方法區,所以當永久代空間不足時同樣會導致Full GC。

這裏補充一下永久代的歷史,有興趣的可以看看。其實用永久代來實現方法區並不是一件好事,因爲這樣更容易導致方法區內存溢出(永久代有-XX:MaxPermSize上限,即使不設置也有默認大小,而對於不使用永久代的虛擬機來說,只要不超過機器內存就不會有問題。)

所以從JDK6開始HotSpot就有放棄永久代的計劃,到JDK7時期,已經將字符串常量池和類的靜態變量移到了Java堆,終於到了JDK8之後這一概念被完全拋棄,改用元空間來實現方法區,永久代成了永久的回憶。

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