聲明:本文摘抄自《深入理解Java虛擬機(jī)》一書享扔,本文完全為自我學(xué)習(xí)瘩蚪,請(qǐng)感興趣的同學(xué)購(gòu)買正版躲株,支持原創(chuàng)
Java內(nèi)存管理主要解決兩個(gè)問題:給對(duì)象分配內(nèi)存和回收分配給對(duì)象的內(nèi)存坝茎。
對(duì)象內(nèi)存分配涤姊,從大的方向來(lái)將,就是在堆上分配嗤放,對(duì)象主要分配在新生代的Eden空間思喊,如果啟用了本地線程分配緩沖,將按線程優(yōu)先級(jí)在TLAB上分配次酌。少數(shù)情況下也會(huì)直接分配到老年代中恨课,分配的規(guī)則并不是百分百固定的,其細(xì)節(jié)取決于采用哪種垃圾收集器組合岳服,還有虛擬機(jī)與內(nèi)存相關(guān)的參設(shè)置剂公。
對(duì)象優(yōu)先在Eden分配
大多數(shù)情況下,對(duì)象在新生代Eden空間分配吊宋,當(dāng)Eden沒有足夠空間進(jìn)行分配時(shí)纲辽,虛擬機(jī)將發(fā)起一次Minor GC。
虛擬機(jī)提供了-XX:+PrintGCDetails
這個(gè)收集器日志參數(shù)璃搜,告訴虛擬機(jī)在發(fā)生垃圾收集行為時(shí)打印內(nèi)存回收日志拖吼,并且在進(jìn)程退出時(shí)輸出當(dāng)前內(nèi)存各個(gè)區(qū)域的分配情況。
注意
- 新生代GC(Minor GC): 指發(fā)生在新生代的垃圾收集動(dòng)作这吻,因?yàn)镴ava對(duì)象大多具備朝生夕死的特性绿贞,所以Minor GC會(huì)比較頻繁,一般回收速度也比較快橘原。
- 老年代GC(Major GC / Full GC):指發(fā)生在老年代的GC籍铁,出現(xiàn)了Major GC,經(jīng)常會(huì)伴隨著一次或多次Minor GC趾断。Major GC的速度一般會(huì)比Minor GC慢10倍以上拒名。
大對(duì)象直接進(jìn)入老年代
所謂的大對(duì)象是指,需要大量連續(xù)內(nèi)存空間的Java對(duì)象芋酌,最典型的大對(duì)象就是很長(zhǎng)的字符串或數(shù)組增显。大對(duì)象的分配對(duì)虛擬機(jī)來(lái)說(shuō)是一個(gè)壞消息,經(jīng)常出現(xiàn)大對(duì)象容易導(dǎo)致內(nèi)存還有不少空間時(shí)就提前觸發(fā)垃圾收集以獲得足夠的連續(xù)空間來(lái)”安置“他們脐帝。
虛擬機(jī)提供了一個(gè)-XX:PretenureSizeThreshold
參數(shù)同云,令大于這個(gè)設(shè)置值的對(duì)象直接在老年代分配。這樣做的目的是避免在Eden空間以及兩個(gè)Survivor空間發(fā)生大量的內(nèi)存復(fù)制堵腹。
長(zhǎng)期存活的對(duì)象直接進(jìn)入老年代
既然虛擬機(jī)采用分代收集的思想來(lái)管理內(nèi)存炸站,那么內(nèi)存回收就必須識(shí)別哪些對(duì)象應(yīng)該放在新生代,哪些對(duì)象應(yīng)該放入老年代疚顷。為了做到這一點(diǎn)旱易,虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡(Age)的計(jì)數(shù)器禁偎。如果對(duì)象在Eden空間出生并經(jīng)過(guò)第一次Minor GC后仍然存活,并且能夠被Survivor容納的話阀坏,將被移動(dòng)到Survivor空間如暖,并且對(duì)象年齡設(shè)置為1。對(duì)象在Survivor空間每”熬過(guò)“一次Minor GC忌堂,年齡就增加1歲盒至,當(dāng)年齡增加到一定程度(默認(rèn)為15歲),就將會(huì)被晉升到老年代中士修。對(duì)象晉升老年代的年齡閾值枷遂,可以通過(guò)參數(shù)-XX:MaxTenuringThreshold
設(shè)置。
動(dòng)態(tài)對(duì)象年齡判定
為了能更好地適應(yīng)不同程序的內(nèi)存狀況李命,虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshod才能晉升老年代登淘,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半箫老,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代封字,無(wú)須等到MaxTenuringThreshod要求的年齡。
空間分配擔(dān)保
在發(fā)生Minor GC前耍鬓,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間阔籽,如果這個(gè)條件成立,那么Minor GC可以確保是安全的牲蜀。如果不成立笆制,則虛擬機(jī)會(huì)查看-XX:HandlePromotionFailure
設(shè)置值是否允許擔(dān)保失敗。如果允許涣达,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均水平在辆,如果大于,將嘗試進(jìn)行一次Minor GC度苔,盡管這次Minor GC是有風(fēng)險(xiǎn)的匆篓;如果小于,或者-XX:HandlePromotionFailure
設(shè)置不允許冒險(xiǎn)寇窑,那這時(shí)要改為進(jìn)行一次Full GC鸦概。
取平均值進(jìn)行比較其實(shí)仍然是一種動(dòng)態(tài)概率的手段,也就是說(shuō)甩骏,如果某次Minor GC后窗市,存活對(duì)象突增,遠(yuǎn)高于平均值的話饮笛,依然會(huì)出現(xiàn)擔(dān)保失斪刹臁(Handle Promotion Failure)。如果出現(xiàn)擔(dān)保失敗福青,那就只好在失敗后重新發(fā)起一次Full GC扎拣。雖然擔(dān)保失敗時(shí)繞的圈子是最大的,但大部分情況下都還是將擔(dān)保失敗的開關(guān)打開,避免Full GC過(guò)于頻繁二蓝。
在JDK6 Update24之后誉券,雖然JVM源碼中還定義了HandlePromotionFailure參數(shù),但是在代碼中已經(jīng)不會(huì)再使用它刊愚。JDK6 Update24之后的規(guī)則變?yōu)橹灰夏甏倪B續(xù)空間大于新生代對(duì)象總大小或者歷次晉升的平均大小就會(huì)進(jìn)行Minor GC踊跟,否則將進(jìn)行Full GC。