Java垃圾收集機(jī)制,看這一篇文章就夠了

1.概述

在這篇文章中,我主要會(huì)講到以下幾個(gè)內(nèi)容:

(1)“垃圾”主要存在于什么位置?

(2)什么是“垃圾”?

(3)如何去清理“垃圾”?

(4)擴(kuò)展:內(nèi)存的分類策略

2.“垃圾”主要存在于什么位置?

要進(jìn)行清理之前,Java虛擬機(jī)需要知道哪些空間是需要進(jìn)行自動(dòng)清理回收的,所以,我們首先大致了解一下Java虛擬機(jī)的內(nèi)存空間布局。

Java內(nèi)存主要分為五個(gè)部分:

(1)程序計(jì)數(shù)器:用于存儲(chǔ)當(dāng)前指令所在的地址,一旦當(dāng)前指令執(zhí)行結(jié)束,程序計(jì)數(shù)器自動(dòng)加1或者根據(jù)轉(zhuǎn)移地址跳轉(zhuǎn)到下一條指令。對(duì)于單核CPU來(lái)說(shuō),線程之間是通過(guò)不斷切換獲得CPU資源來(lái)實(shí)現(xiàn)宏觀上的并行,那么就需要每一個(gè)線程有自己?jiǎn)为?dú)的程序計(jì)數(shù)器,以便在獲得CPU資源后可以從斷點(diǎn)的位置繼續(xù)執(zhí)行指令。所以,程序計(jì)數(shù)器是線程私有的內(nèi)存區(qū)域。

(2)虛擬機(jī)棧:堆和棧的概念相信大家已經(jīng)很熟悉了,簡(jiǎn)單來(lái)說(shuō),棧中存放的是一個(gè)又一個(gè)的棧幀,當(dāng)一個(gè)方法被調(diào)用時(shí),虛擬機(jī)就會(huì)創(chuàng)建一個(gè)棧幀并將其壓入棧。棧幀主要是用于存放局部變量、操作數(shù)棧、動(dòng)態(tài)鏈接以及方法出口等等信息。而且,棧內(nèi)存也是線程私有的,每一個(gè)線程會(huì)在棧內(nèi)存內(nèi)有一塊自己私有的區(qū)域。

(3)本地方法棧:本地方法棧存儲(chǔ)的內(nèi)容和虛擬機(jī)棧很類似,唯一的區(qū)別是本地方法棧是為虛擬機(jī)執(zhí)行Native方法而服務(wù),虛擬機(jī)棧是為了虛擬機(jī)執(zhí)行一般的Java方法而服務(wù),本地方法棧也是線程私有的內(nèi)存區(qū)。

(4)Java堆:堆中主要存放的是對(duì)象的實(shí)體以及數(shù)組等內(nèi)容(對(duì)象的引用被存放在了棧中),Java堆是被所有線程所共享的一塊區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)就會(huì)被創(chuàng)建。

(5)方法區(qū):方法區(qū)和堆一樣,是線程共享的內(nèi)存區(qū)域,方法區(qū)主要用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量和即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。

大致講了一下Java內(nèi)存的五個(gè)部分,主要是想說(shuō)明,這五個(gè)部分中,程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧都是線程私有的內(nèi)存區(qū)域,其存儲(chǔ)的是數(shù)據(jù)會(huì)隨著線程的消亡而消亡,所以不需要虛擬機(jī)為其清理垃圾,也就是說(shuō),Java堆和方法區(qū)是垃圾回收機(jī)制(后面我們會(huì)稱其為GC機(jī)制)的“主要照顧對(duì)象”。

2.什么是“垃圾”?

上面我們說(shuō)到,虛擬機(jī)主要為我們清理的是堆和方法區(qū)兩個(gè)區(qū)域,那么主要清理的內(nèi)容就是無(wú)用的對(duì)象、常量、靜態(tài)變量和類信息。

那么,如何判斷一個(gè)對(duì)象、常量、靜態(tài)變量或者類是無(wú)用的呢?

這里我們將對(duì)象、常量、靜態(tài)變量放在一起來(lái)說(shuō),因?yàn)樗鼈兊呐袛喾绞绞且恢碌摹O旅嫖覀円詫?duì)象為例,具體談一下這種判斷機(jī)制。

最初,虛擬機(jī)是使用引用計(jì)數(shù)算法來(lái)判斷一個(gè)對(duì)象是否有reference指向它。引用計(jì)數(shù)算法的原理是,每一個(gè)對(duì)象都有一個(gè)計(jì)數(shù)器,當(dāng)有一個(gè)reference指向該對(duì)象是,計(jì)算器便+1,當(dāng)減少一個(gè)reference時(shí),計(jì)數(shù)器便-1,一旦計(jì)數(shù)器值為零,恰好這時(shí)候虛擬機(jī)進(jìn)行了一次GC,那么該對(duì)象就會(huì)被清除。

這種算法雖然實(shí)現(xiàn)簡(jiǎn)單,而且效率高,但是其有一個(gè)很大的缺點(diǎn),如果兩個(gè)對(duì)象互相引用,即使兩個(gè)對(duì)象在程序中不會(huì)再被使用到,但是兩個(gè)對(duì)象的計(jì)算器一直保持著1,虛擬機(jī)便永遠(yuǎn)不會(huì)清除這兩個(gè)對(duì)象。也就是說(shuō),這種算法無(wú)法識(shí)別出相互引用的垃圾。

為了解決這個(gè)問(wèn)題,可達(dá)性分析算法誕生。這個(gè)算法的基本思想是通過(guò)一系列的被稱之為“GC Roots”的對(duì)象作為起始點(diǎn),從這些對(duì)象開始搜索,找到其引用的下一個(gè)對(duì)象,直到最后的對(duì)象沒(méi)有下一個(gè)引用。這樣搜尋出的對(duì)象形成一個(gè)“引用鏈”,我們認(rèn)為,在“引用鏈”上的對(duì)象是不能被清理的,不在“引用鏈”上的對(duì)象,進(jìn)入到被清理的“邊緣”。(請(qǐng)注意,我這里說(shuō)的是在邊緣,也就是說(shuō)這些對(duì)象還有一次自救的機(jī)會(huì)。)


如上圖所示,對(duì)象6和對(duì)象7雖然互相引用,但是因?yàn)椴辉谝面溕?,也?huì)被看做是垃圾。

在Java中,可以作為GC Roots的對(duì)象包括以下幾種:

(1)虛擬機(jī)棧中引用的對(duì)象;

(2)方法區(qū)中靜態(tài)屬性引用的對(duì)象;

(3)方法區(qū)中常量引用的對(duì)象;

(4)本地方法棧中本地方法引用的對(duì)象;

但是,一個(gè)對(duì)象不在引用鏈上,就一定會(huì)被看做成垃圾直接清除掉了嗎?

其實(shí)虛擬機(jī)的判斷沒(méi)有這樣草率,當(dāng)進(jìn)行一次可達(dá)性分析之后,虛擬機(jī)會(huì)將不在引用鏈上的對(duì)象添加一個(gè)標(biāo)記(他們已經(jīng)處于很危險(xiǎn)的狀態(tài)),這時(shí),虛擬機(jī)會(huì)去判斷這些很危險(xiǎn)的對(duì)象有沒(méi)有必要執(zhí)行finalize()方法(繼承自O(shè)bject類),如果該對(duì)象重寫了finalize()方法而且該finalize()方法從未被執(zhí)行過(guò),那么虛擬機(jī)會(huì)給這個(gè)對(duì)象一個(gè)執(zhí)行finalize()方法的機(jī)會(huì),如果執(zhí)行完該方法后,這個(gè)對(duì)象被引用鏈上的某個(gè)對(duì)象所重新引用,那么,恭喜這個(gè)對(duì)象,它不會(huì)被清除了。如果執(zhí)行完finalize()方法后,這個(gè)對(duì)象還是沒(méi)有進(jìn)入引用鏈或者虛擬機(jī)認(rèn)為這個(gè)對(duì)象沒(méi)有必要執(zhí)行finalize()方法,那么,對(duì)不起,這時(shí)這個(gè)對(duì)象就真的被認(rèn)為是“垃圾”需要清理掉。

剛剛說(shuō)到的是如何判斷對(duì)象是不是需要被清理,判斷常量、靜態(tài)變量也是如此,那么,一個(gè)類該不該被清理應(yīng)如何判斷呢?

判斷一個(gè)類是無(wú)用的類必須滿足以下三個(gè)條件:

(1)該類的所有實(shí)例均已被回收,Java堆中不存在該類的實(shí)例;

(2)該類的ClassLoader已經(jīng)被回收;

(3)該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法;

3.如何清理“垃圾”?

當(dāng)虛擬機(jī)知道了垃圾的位置,也知道了什么時(shí)垃圾,下一步我們需要為其指定清除垃圾的方法。

垃圾收集算法

標(biāo)記—清除算法:這是一種最基礎(chǔ)的收集算法,它的工作流程就如同它的名字一樣,分為標(biāo)記和清除兩個(gè)過(guò)程。首先標(biāo)記出所有需要回收的對(duì)象(就是在判斷對(duì)象是否是垃圾),在標(biāo)記結(jié)束后統(tǒng)一清除所有被標(biāo)記的對(duì)象。但是幾乎沒(méi)有虛擬機(jī)直接采用了這種這種收集算法,首先,標(biāo)記和清除兩個(gè)過(guò)程效率均比較低,而且更重要的是,清除過(guò)后,內(nèi)存空間會(huì)留下很多不連續(xù)的碎片,導(dǎo)致后續(xù)需要存儲(chǔ)大對(duì)象時(shí)無(wú)法找到足夠的連續(xù)內(nèi)存,不得不提前觸發(fā)GC動(dòng)作。

標(biāo)記—整理算法:為了解決標(biāo)記—清除算法造成內(nèi)存空間碎片較多的問(wèn)題,誕生了標(biāo)記—整理算法。顧名思義,該算法也分為兩個(gè)部分,標(biāo)記和整理。標(biāo)記過(guò)程仍然和“標(biāo)記—清除算法”相同,但是標(biāo)記過(guò)后,沒(méi)有直接進(jìn)行清理,而是把所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存,這樣一來(lái),經(jīng)過(guò)一次清理,內(nèi)存空間便不存在碎片,一端是存活的對(duì)象,另一端是未使用的空間。

復(fù)制算法:也是一種比較主流的收集算法,將內(nèi)存區(qū)域平均分成兩部分,每次只使用其中的一塊,當(dāng)這一塊空間填滿之后,就將這一塊內(nèi)存中存活對(duì)象復(fù)制到另外一塊中,接下來(lái)將使用過(guò)的這一塊內(nèi)存空間清空,然后開始使用另外一塊內(nèi)存空間。這樣在分配內(nèi)存上只需要按順序分配即可,效率得到了提高,而且也沒(méi)有碎片的問(wèn)題,但是,每次只有一半的空間可供使用,代價(jià)未免太高了一點(diǎn)。

分代回收算法:上面提到的幾種算法各有各的好處,復(fù)制算法在需要清除的對(duì)象比較多的時(shí)候會(huì)有很高的運(yùn)行效率(因?yàn)橹恍枰獙⒁恍〔糠执婊畹膶?duì)象復(fù)制到新的內(nèi)存空間),標(biāo)記—整理算法在要清除的對(duì)象比較少的時(shí)候效率比較高(幾乎不需要移動(dòng)和清除),所以能不能將內(nèi)存空間按照存活對(duì)象的多少來(lái)進(jìn)一步劃分呢?這就是分代回收算法的基本思想。這種回收算法將Java堆內(nèi)存劃分成新生代和老年代,新生代里的對(duì)象一般存活周期比較短,老年代中的對(duì)象存活周期比較長(zhǎng)。那么在新生代中就可以采用復(fù)制算法,老年代中采用標(biāo)記—整理算法。而且,研究表明,新生代中每次GC,大約98%的對(duì)象都是需要被清除的,這樣一來(lái),我們就不需要將新生代劃分為大小相等的兩份進(jìn)行復(fù)制。而是按照8:1:1的比例劃分為三份,空間較大的一份被稱為Eden區(qū),空間較小的兩份被稱為Survivor區(qū)(一個(gè)被稱為from區(qū),一個(gè)被稱為to區(qū))。使用的時(shí)候?qū)?duì)象存進(jìn)Eden區(qū)和From區(qū),回收時(shí),將Eden區(qū)和From區(qū)的存活對(duì)象復(fù)制到To區(qū)中,然后將From區(qū)和To區(qū)進(jìn)行對(duì)換。這就完成了一次新生代的垃圾回收。如果To區(qū)裝載不下存活的對(duì)象。需要依賴?yán)夏甏M(jìn)行分配擔(dān)保(將存活的對(duì)象存進(jìn)老年代)。一旦老年代也無(wú)法存滿了之后,將會(huì)觸發(fā)一次Full GC,將老年代和永久代(方法區(qū))使用標(biāo)記—整理算法進(jìn)行一次清理,這一次的清理時(shí)間會(huì)很長(zhǎng),所以,應(yīng)盡可能少的觸發(fā)Full GC。

垃圾收集器

如果說(shuō)收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。

在JDK1.7之后的HotSpot虛擬機(jī)上,包含有如下這么多的收集器的實(shí)現(xiàn)。

上半部分是用在新生代的垃圾收集器,下半部分是用在老年代的垃圾收集器。用線連接起來(lái)說(shuō)明這兩種收集器可以配合使用。

下面簡(jiǎn)要說(shuō)明每一種垃圾收集器的特性。但是要明確一點(diǎn),沒(méi)有最好的收集器,只有更合適收集器。否則HotSpot虛擬機(jī)就沒(méi)有必要實(shí)現(xiàn)這么多不同的收集器了。

Serial收集器是一個(gè)用在新生代上的垃圾收集器,所以使用的是復(fù)制算法,而且它是一個(gè)單線程的收集器,這不僅僅說(shuō)明它只會(huì)使用一條線程去完成垃圾收集工作,更重要的是,它在進(jìn)行垃圾收集工作時(shí),必須暫停其他所有的工作線程,直到它收集結(jié)束。這個(gè)工作機(jī)制被稱為“Stop The World”,對(duì)于用戶來(lái)說(shuō)是很難接受的(用戶的其他所有工作都必須要停頓一段時(shí)間)。所以,目前大部分垃圾收集器都在致力于如何縮短這一停頓時(shí)間。盡管Serial收集器的用戶體驗(yàn)不是很好,但是它也有著很明顯的優(yōu)勢(shì):簡(jiǎn)單而高效,在單CPU的工作環(huán)境下,由于沒(méi)有線程之間的交互開銷,效率要比多線程的收集器高許多。所以它現(xiàn)在仍然是虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器。(Client模式下,新生代空間一般不會(huì)很大,收集一次所停頓的時(shí)間也不是很長(zhǎng))

Serial Old收集器是Serial收集器的老年代版本,同樣使用著單線程收集器,但是用的是“標(biāo)記—整理”算法,這個(gè)收集器主要意義在于給Client模式下的虛擬機(jī)在老年代使用。在Server模式下,它也可以與?Parallel Scavenge搭配使用,或者作為CMS收集器的后備預(yù)案。


Serial / Serial Old收集器運(yùn)行示意圖

ParNew收集器其實(shí)就是Serial收集器的多線程版本,也是用在新生代上,也是用的復(fù)制算法,在垃圾回收時(shí),仍然要“Stop The World”,只不過(guò)就是可以用多個(gè)線程共同完成垃圾清理工作。目前,ParNew收集器是許多運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器,原因是這樣的,在jdk1.5之后,HotSpot退出了一款有劃時(shí)代意義的垃圾收集器——CMS(Concurrent Mark Sweep)收集器,這是一款用于老年代垃圾收集的收集器,不幸的是,能夠與其配合的用在新生代的收集器只有Serial和ParNew收集器,在Server模式下,隨著CPU數(shù)量的增加,ParNew收集器(并行進(jìn)行垃圾清除)要比Serial收集器(單線程垃圾清除)能更好地利用CPU資源。

Parallel Scavenge收集器也是一個(gè)新生代收集器,也使用了復(fù)制算法,也是并行的多線程收集器,乍一看,和ParNew收集器是一樣的。但是它的關(guān)注點(diǎn)不同,其他的收集器都在致力于縮短用戶線程的停頓時(shí)間,但是Parallel Scavenge收集器致力于達(dá)到一個(gè)可控制的吞吐量。Parallel Scavenge收集器提供了兩個(gè)參數(shù),-XX:MaxGCPauseMillis 和 -XX:GCTimeRatio,第一個(gè)參數(shù)設(shè)定的是每一次停頓的最長(zhǎng)時(shí)間,第二個(gè)參數(shù)設(shè)定的是垃圾收集時(shí)間占總時(shí)間的比率。

這里提一下,不一定是停頓時(shí)間越短,垃圾收集時(shí)間占的比例就小,舉個(gè)例子,當(dāng)?-XX:MaxGCPauseMillis 參數(shù)設(shè)置的比較小時(shí),雖然每次停頓時(shí)間變短,但是代價(jià)是新生代的容量減小,隨之而來(lái)的后果便是,GC變得更加頻繁,垃圾收集時(shí)間占的比例增大,吞吐量降低。

同時(shí),Parallel Scavenge收集器還有一個(gè)優(yōu)點(diǎn)是,可以根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況動(dòng)態(tài)調(diào)節(jié)與GC相關(guān)的參數(shù)以提供最合適的停頓時(shí)間或者最大的吞吐量。

Parallel Old收集器是?Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記—整理”算法、這個(gè)收集器在jdk1.6之后才開始提供,在此之前,如果新生代選擇的是Parallel Scavenge收集器,那么老年代只有Serial Old收集器與之對(duì)應(yīng),但是Serial Old收集器會(huì)嚴(yán)重拖累Parallel Scavenge收集器獲取最大吞吐量的效果,直到Parallel Old收集器的出現(xiàn),“吞吐量?jī)?yōu)先”的收集器組合才名副其實(shí),所以,在注重吞吐量以及CPU資源的場(chǎng)合,可以選擇Parallel Scavenge和Parallel Old組合。


Parallel Scavenge / Parallel Old 收集器運(yùn)行示意圖

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。它是基于“標(biāo)記—清除”算法實(shí)現(xiàn)的。它的運(yùn)作過(guò)程分為以下四個(gè)步驟:

初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、并發(fā)清除

在進(jìn)行初始標(biāo)記和重新標(biāo)記時(shí),仍需要“Stop The World”,在并發(fā)標(biāo)記和并發(fā)清除兩個(gè)過(guò)程,用戶的進(jìn)程可以和GC進(jìn)程并發(fā)執(zhí)行。

初始標(biāo)記僅僅是標(biāo)記一下“GC Roots”可以直接關(guān)聯(lián)到的對(duì)象,速度很快;并發(fā)標(biāo)記便是尋找整個(gè)引用鏈的過(guò)程,但是這個(gè)階段,用戶的進(jìn)程有可能會(huì)改變一些對(duì)象的引用;那么重新標(biāo)記就是為了標(biāo)記那些有變動(dòng)的對(duì)象,這個(gè)階段耗時(shí)會(huì)比初始標(biāo)記稍長(zhǎng),但是遠(yuǎn)遠(yuǎn)小于并發(fā)標(biāo)記過(guò)程。也就是說(shuō),耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除兩個(gè)過(guò)程是不需要用戶線程停頓的,停頓的階段只是初始標(biāo)記和重新標(biāo)記兩個(gè)短暫的過(guò)程,所以號(hào)稱是目前最短回收停頓時(shí)間的收集器。

CMS收集器運(yùn)行示意圖

以上便介紹完了目前常用的幾種收集器,還是那句話,沒(méi)有最好的收集器,我們需要依據(jù)系統(tǒng)的運(yùn)行情況選擇最合適的收集器組合方式。

4.內(nèi)存的分配策略

上面說(shuō)過(guò),創(chuàng)建一個(gè)新的對(duì)象后,這個(gè)對(duì)象會(huì)被存進(jìn)新生代的Eden區(qū)中,如果沒(méi)有足夠的空間存放,將會(huì)觸發(fā)一次新生代的GC,被稱之為Minor GC(老年代的GC被稱為Full GC或者M(jìn)ajor GC)。但是,這個(gè)說(shuō)法并不準(zhǔn)確,如果新創(chuàng)建的對(duì)象非常大,比如說(shuō)是一個(gè)很長(zhǎng)的字符串或者很長(zhǎng)的數(shù)組,那么這個(gè)對(duì)象很有可能被直接放進(jìn)老年代。虛擬機(jī)提供了一個(gè)-XX:PretenureSizeThreshold參數(shù),令大于這個(gè)設(shè)置值的對(duì)象直接存進(jìn)老年代,這樣就可以避免在Eden區(qū)以及兩個(gè)Servivor區(qū)來(lái)回復(fù)制。

那么,除了大對(duì)象可以直接放進(jìn)老年代,還有什么情況下,新生代的對(duì)象可以被存進(jìn)老年代中呢?

(1)長(zhǎng)期存活的對(duì)象將進(jìn)入老年代

虛擬機(jī)為每一個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器,如果對(duì)象在Eden出生并經(jīng)過(guò)第一次Minor GC仍然存活,年齡計(jì)數(shù)器便會(huì)置為1,后來(lái)每經(jīng)過(guò)一次Minor GC,年齡計(jì)數(shù)器就會(huì)增加1,當(dāng)對(duì)象的年齡增加到一定程度(默認(rèn)15歲),就會(huì)晉升到年老代。這個(gè)年齡閾值是可以通過(guò)-XX:MaxTenuringThreshold設(shè)置。

(2)動(dòng)態(tài)對(duì)象年齡判定

為了能更好地適應(yīng)不同程序的內(nèi)存情況,虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升到老年代,如果在Survivor空間中相同年齡對(duì)象大小的總和大于Survivor空間的一半,那么年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代。

(3)空間分配擔(dān)保

空間分配擔(dān)保在分代回收算法中提到過(guò)一次,不過(guò)說(shuō)的比較粗略。下面詳細(xì)說(shuō)一下這個(gè)概念中的一些細(xì)節(jié)。

當(dāng)新生代需要進(jìn)行一次Minor GC時(shí),虛擬機(jī)會(huì)先去檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那么Minor GC可以確保是安全的(因?yàn)榧词筍urvivor區(qū)的To區(qū)無(wú)法存放得下存活的對(duì)象,這些對(duì)象也完全可以由年老區(qū)分配空間存放,這就是分配擔(dān)保的意思)。但是,如果這個(gè)條件不成立,那么虛擬機(jī)會(huì)去查看handlePromotionFailure設(shè)置值是否允許擔(dān)保失?。ㄒ?yàn)檫@時(shí),如果新生代全都是存活的對(duì)象,老年代就沒(méi)有足夠的空間容納這么多對(duì)象,所以現(xiàn)在是需要承擔(dān)擔(dān)保失敗的風(fēng)險(xiǎn)的),如果允許,虛擬機(jī)會(huì)繼續(xù)查看老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,則開始Minor GC,如果不大于或者h(yuǎn)andlePromotionFailure設(shè)置的是不允許承擔(dān)擔(dān)保失敗,那么將會(huì)提前觸發(fā)一次Full GC。

下面解釋一下這里承擔(dān)的是什么風(fēng)險(xiǎn),現(xiàn)在已經(jīng)確定老年代中最大可用的連續(xù)空間小于新生代的所有對(duì)象總空間,我們也是通過(guò)以往的經(jīng)驗(yàn)值(歷次晉升到老年代的對(duì)象大小的平均值)認(rèn)為這一次老年代仍然有足夠的空間容納新生代中存活的對(duì)象,但是,一旦這一次Minor GC比較特殊,絕大多數(shù)對(duì)象都存活了下來(lái),其大小遠(yuǎn)遠(yuǎn)超過(guò)經(jīng)驗(yàn)值,老年代沒(méi)有足夠空間存放,那么相當(dāng)于繞了個(gè)大圈,最后還是要觸發(fā)Full GC,這便是需要承擔(dān)的風(fēng)險(xiǎn)。

下面這張圖表示如何為一個(gè)對(duì)象分配內(nèi)存空間。


以上內(nèi)容便是關(guān)于Java 垃圾回收部分的全部基礎(chǔ)知識(shí),要想更近一步地了解JVM(Java虛擬機(jī))在底層是如何工作,請(qǐng)大家前往閱讀

《深入了解Java虛擬機(jī)》這本書,該書中提供了大量代碼,大家可以自己動(dòng)手,通過(guò)查看GC日志的方式了解JVM垃圾收集是如何工作的。


本文大部分也參照了這本書上垃圾回收器這一章節(jié)的內(nèi)容。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容