一蒙袍、判斷對(duì)象的狀態(tài)
在垃圾收集器對(duì)堆進(jìn)行回收前俊卤,要先確定對(duì)象的狀態(tài):是還存活著,還是已經(jīng)死去害幅。
引用計(jì)數(shù)算法
做法:給對(duì)象一個(gè)引用計(jì)數(shù)器消恍,每當(dāng)有對(duì)象引用它時(shí),計(jì)數(shù)器就加1以现;當(dāng)引用失效時(shí)狠怨,計(jì)數(shù)器就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的邑遏。
引用計(jì)數(shù)算法實(shí)現(xiàn)簡(jiǎn)單取董,判定效率高,在大部分情況下都是一個(gè)不錯(cuò)的算法无宿。但主流的Java虛擬機(jī)里都沒(méi)有選用該算法來(lái)管理內(nèi)存:它很難解決對(duì)象之間的相互循環(huán)引用的問(wèn)題。可達(dá)性分析算法
該算法的基本思想是:通過(guò)一系列的稱為GC Roots
的對(duì)象作為起始點(diǎn)枢里,從這些節(jié)點(diǎn)向下搜索孽鸡,搜索所走過(guò)的路徑稱為引用鏈(Reference Chain
)。當(dāng)一個(gè)對(duì)象到GC Roots
沒(méi)有任何引用鏈相連(從GC Roots
到這個(gè)對(duì)象不可達(dá))栏豺,則證明該對(duì)象不可用彬碱。
在Java中,可作為GC Roots
的對(duì)象包括下面幾種:虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
方法區(qū)中靜態(tài)屬性引用的對(duì)象
方法區(qū)中常量引用的對(duì)象
本地方法棧中JNI(一般說(shuō)的Native方法)引用的對(duì)象
引用的分類</br>
為什么要分類
希望能描述這樣一類對(duì)象:當(dāng)內(nèi)存空間還足夠時(shí)奥洼,則能保留在內(nèi)存中巷疼;如果內(nèi)存空間在進(jìn)行垃圾收集后還是非常緊張,則可以拋棄這些對(duì)象灵奖。很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用對(duì)象嚼沿。</br>
JDK1.2之后估盘,Java對(duì)引用的概念進(jìn)行可擴(kuò)充,將引用分為(引用強(qiáng)度依次減低的順序):強(qiáng)引用骡尽、軟引用遣妥、弱引用、虛引用攀细。</br>
強(qiáng)引用:是在在程序代碼中類似Object obj = new Object()
這類的普遍存在的引用箫踩。只要強(qiáng)引用還在,垃圾收集器就不會(huì)回收被引用的對(duì)象谭贪。
軟引用:用來(lái)描述有用但非必須的對(duì)象境钟。在系統(tǒng)將要發(fā)生內(nèi)存溢出異常時(shí),會(huì)把這些對(duì)象進(jìn)行回收俭识,如果還是內(nèi)存不足慨削,才會(huì)拋內(nèi)存不足異常。使用SoftReference類來(lái)實(shí)現(xiàn)軟引用鱼的。
弱引用:描述非必須對(duì)象理盆,將會(huì)在下一次垃圾回收的時(shí)候進(jìn)行回收(無(wú)論內(nèi)存是否夠用)。使用WeakReference類來(lái)實(shí)現(xiàn)弱引用凑阶。
虛引用:一個(gè)對(duì)象是否有虛引用的存在猿规,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)一個(gè)虛引用來(lái)獲取一個(gè)實(shí)例對(duì)象宙橱。為一個(gè)對(duì)象設(shè)置一個(gè)虛引用的唯一目的就是:在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知姨俩。使用PhantomReference類來(lái)實(shí)現(xiàn)虛引用。
-
方法區(qū)的回收
方法區(qū)中垃圾回收的性價(jià)比較低(很少可以被回收)师郑。HotSpot虛擬機(jī)中方法區(qū)使用永久代來(lái)實(shí)現(xiàn)的环葵。
永久代的垃圾回收主要包括兩部分:廢棄常量和無(wú)用的類。
對(duì)于廢棄常量的判斷與Java堆中的對(duì)象判斷類似宝冕。
而類則要同時(shí)滿足下面的3個(gè)條件才能算是無(wú)用的類:
a.該類的所有實(shí)例都已經(jīng)被回收张遭,也就是堆中不存在該類的任何實(shí)例
b.加載該類的ClassLoader已經(jīng)被回收
c.該類對(duì)應(yīng)的java.lang.Class
對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法地梨。
滿足上面3個(gè)條件后菊卷,并不一定會(huì)回收,只是可以回收了宝剖。是否對(duì)類進(jìn)行回收洁闰,HotSpot提供了-Xnoclassgc參數(shù)進(jìn)行控制。
二万细、垃圾收集算法
標(biāo)記-清除算法
該算法分為兩個(gè)階段:標(biāo)記和清除扑眉,首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成之后,統(tǒng)一回收所有被標(biāo)記的對(duì)象腰素。
不足之處:一是效率問(wèn)題聘裁,標(biāo)記和清除兩個(gè)過(guò)程的效率都不高;二是空間問(wèn)題耸弄,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片咧虎,空間碎片太多可能會(huì)導(dǎo)致以后再程序中需要分配較大對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾回收動(dòng)作计呈。復(fù)制算法
復(fù)制算法將內(nèi)存按容量劃分為大小相等的兩塊砰诵,每次只使用其中的一塊。當(dāng)這一塊內(nèi)存用完捌显,就將還存活的對(duì)象復(fù)制到另一塊上茁彭,然后再把已使用的內(nèi)存空間一次清理掉。這樣扶歪,每次回收只是針對(duì)整個(gè)半?yún)^(qū)理肺,且不會(huì)產(chǎn)生內(nèi)存碎片問(wèn)題。
該算法實(shí)現(xiàn)簡(jiǎn)單善镰,運(yùn)行高效妹萨,但代價(jià)是將內(nèi)存縮小了一半。
現(xiàn)在商業(yè)虛擬機(jī)均采用該算法對(duì)新生代進(jìn)行回收炫欺,但并不按照1:1的比例劃分內(nèi)存乎完。而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間。每次使用Eden空間和其中一塊Survivor空間品洛。當(dāng)回收時(shí)树姨,將Eden空間和Survivor空間中還存活的對(duì)象復(fù)制到另一塊Survivor空間中,最后清理掉Eden空間和之前用過(guò)的Survivor空間桥状。(適用于對(duì)象存活率較低的場(chǎng)景)
HotSpot中默認(rèn)Eden:Survivor:Survivor = 8:1:1
帽揪。
如果在回收時(shí),有大于10%時(shí)的對(duì)象都存活辅斟,即預(yù)留的Survivor空間不夠用转晰,就要依賴其他內(nèi)存(老年代)進(jìn)行分配擔(dān)保:直接進(jìn)入老年代。標(biāo)記-整理算法
針對(duì)老年代的一種算法士飒。該算法在標(biāo)記階段與標(biāo)記-清除算法一致查邢,但后續(xù)并不對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng)变汪,然后直接清理掉端邊界以外的內(nèi)存。分代收集算法
顧名思義蚁趁,根據(jù)對(duì)象的存活周期的不同將內(nèi)存劃分為幾塊,一般是將堆分為新生代和老年代,然后根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?br> 在新生代史翘,每次垃圾回收時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,只有少量存活庐完,就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集徘熔。
在老年代门躯,由于對(duì)象存活率高、沒(méi)有額外空間對(duì)它進(jìn)行分配擔(dān)保酷师,就必須使用標(biāo)記-清除或者標(biāo)記-整理算法進(jìn)行回收讶凉。
三、垃圾收集器
收集算法是內(nèi)存回收的方法論山孔,而垃圾收集器則是內(nèi)存回收的具體實(shí)現(xiàn)懂讯。沒(méi)有萬(wàn)能的收集器,沒(méi)有最好的收集器台颠,只是對(duì)具體應(yīng)用選擇最合適的收集器褐望。
Serial收集器
單線程的新生代收集器,在它進(jìn)行垃圾收集時(shí)串前,必須暫停其他所有工作線程瘫里,直到它收集結(jié)束。
采用復(fù)制算法
簡(jiǎn)單而高效(與其他收集器的單線程比)荡碾,對(duì)于限定的單個(gè)CPU的環(huán)境下谨读,沒(méi)有線程交互的開(kāi)銷,專心做垃圾回收操作玩荠。
是虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器漆腌。
ParNew收集器
其實(shí)就是Serial收集器的多線程版本,其他與Serial收集器完全一樣(比如控制參數(shù)阶冈、收集算法闷尿、Stop The World、對(duì)象分配規(guī)則女坑、回收策略) 填具。默認(rèn)開(kāi)啟的收集線程與CPU數(shù)量相同,在CPU非常多的環(huán)境下匆骗,可使用
-XX:ParallelGCThreads
參數(shù)來(lái)限制垃圾收集的線程數(shù)劳景。是很多運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器:除了Serial收集器,只有它能與CMS收集器配合工作碉就。它也是使用CMS收集器時(shí)默認(rèn)的新生代收集器盟广。
Parallel Scavenge收集器
新生代收集器,使用復(fù)制算法瓮钥,并行的多線程收集器筋量。
該收集器的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量(CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值)烹吵,
吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)
,被稱為吞吐量?jī)?yōu)先的收集器桨武。高吞吐量可以高效率地利用CPU時(shí)間肋拔,盡快完成程序的運(yùn)算任務(wù),適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)呀酸。
設(shè)定兩個(gè)參數(shù)來(lái)控制吞吐量:最大垃圾收集停頓時(shí)間
-XX:MaxGCPauseMills
和吞吐量大小-XX:GCTimeRatio
凉蜂。自適應(yīng)調(diào)節(jié)策略:通過(guò)參數(shù)
-XX:+UseAdaptiveSizePolicy
,開(kāi)啟之后性誉,就不需要手工指定新生代大小窿吩、Eden與Survivor區(qū)的比例、晉升老年代對(duì)象的大小等細(xì)節(jié)參數(shù)艾栋,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)運(yùn)行情況進(jìn)行GC自適應(yīng)調(diào)節(jié)爆存。Serial Old收集器
Serial收集器的老年代版本,單線程蝗砾,使用標(biāo)記-整理算法先较。
主要意義是在給定Client模式下的虛擬機(jī)使用。在Server模式下悼粮,有兩種用途:①在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用闲勺;②作為CMS收集器的后備預(yù)案,在并發(fā)收集發(fā)生
Concurrent Mode Failure
時(shí)使用扣猫。Parallel Old收集器
Parallel Scavenge收集器的老年代版本菜循,多線程,使用標(biāo)記-整理算法申尤。
JDK1.6中才開(kāi)始提供該收集器癌幕。在此之前,新生代如果選擇了Parallel Scavenge收集器昧穿,老年代只能選擇Serial Old收集器勺远。Parallel Old收集器的出現(xiàn),使得“吞吐量?jī)?yōu)先收集器”有了比較名副其實(shí)的組合:
Parallel Scavenge + Parallel Old
时鸵。CMS收集器
老年代收集器
CMS(
Concurrent Mark Sweep
)收集器是一種獲取最短回收停頓時(shí)間為目標(biāo)的收集器胶逢。優(yōu)點(diǎn)是并發(fā)、低停頓饰潜。在互聯(lián)網(wǎng)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端上的Java應(yīng)用初坠,注重服務(wù)的響應(yīng)時(shí)間,希望系統(tǒng)停頓時(shí)間最短彭雾,以帶給用戶較好的體驗(yàn)碟刺,CMS收集器非常適合這類應(yīng)用的需求。基于標(biāo)記-清除算法薯酝,運(yùn)作過(guò)程分為4個(gè)步驟:
a.初始標(biāo)記:需要Stop The World半沽,標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象身诺,速度很快。
b.并發(fā)標(biāo)記:與用戶程序一起運(yùn)行抄囚,進(jìn)行GC Roots Tracing的過(guò)程。
c.重新標(biāo)記:需要Stop The World橄务,修正并發(fā)標(biāo)記階段因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變化的那一部分對(duì)象的標(biāo)記記錄幔托。該階段的停頓時(shí)間比初始階段稍長(zhǎng),但遠(yuǎn)小于并發(fā)標(biāo)記的停頓時(shí)間蜂挪。
d.并發(fā)清除:與用戶程序一起運(yùn)行重挑,進(jìn)行清除工作。CMS遠(yuǎn)達(dá)不到完美的程度棠涮,它有4個(gè)明顯的缺點(diǎn):
a.對(duì)CPU非常敏感:因占用CPU資源導(dǎo)致應(yīng)用程序變慢谬哀,總吞吐量降低。CMS默認(rèn)的垃圾收集線程數(shù)為(CPU數(shù)量 + 3)/4严肪。當(dāng)CPU在4個(gè)以上時(shí)史煎,垃圾回收線程會(huì)至少占用25%的CPU資源,并會(huì)隨著CPU數(shù)量增加而下降驳糯。當(dāng)CPU不足4個(gè)時(shí)篇梭,CMS對(duì)用戶程序的影響可能就非常大。
b.CMS無(wú)法處理浮動(dòng)垃圾酝枢。由于CMS并發(fā)清理階段用戶線程還在運(yùn)行恬偷,可能會(huì)出現(xiàn)新的垃圾,CMS無(wú)法在本次收集時(shí)處理它們帘睦,只能等下一次GC再清理袍患。這部分垃圾就是浮動(dòng)垃圾。
c.由于并發(fā)收集竣付,在收集時(shí)還要預(yù)留內(nèi)存空間供用戶程序使用诡延,因此CMS收集器無(wú)法像其他收集器一樣等到老年代幾乎被完全填充時(shí)再進(jìn)行收集。在JDK1.5的默認(rèn)設(shè)置下卑笨,當(dāng)老年代使用了68%的時(shí)候孕暇,就會(huì)進(jìn)行垃圾收集。如果應(yīng)用中老年代增長(zhǎng)不快赤兴,可適當(dāng)提高該值妖滔。JDK1.6將該值提升到92%。但不能設(shè)置的太高:如果在CMS運(yùn)行期間預(yù)留的內(nèi)存空間無(wú)法滿足程序需要桶良,就會(huì)出現(xiàn)Concurrent Mode Failure
座舍,這時(shí)虛擬機(jī)將會(huì)啟動(dòng)后備預(yù)案:臨時(shí)啟用Serial Old收集器重新進(jìn)行老年代的垃圾收集,導(dǎo)致停頓時(shí)間很長(zhǎng)陨帆∏可以通過(guò)參數(shù)-XX:CMSInitiatingOccupancyFraction
設(shè)置該值采蚀。
d.基于標(biāo)記-清除算法,容易造成內(nèi)存碎片承二,往往出現(xiàn)老年代還有很大空間剩余榆鼠,但無(wú)法找到足夠大的連續(xù)空間來(lái)分配當(dāng)前對(duì)象,不得不出發(fā)一次Full GC亥鸠∽惫唬可以通過(guò)參數(shù)-XX:UseCMSCompactAtFullCollection
(默認(rèn)開(kāi)啟),用于在CMS頂不住要進(jìn)行一次FullGC時(shí)開(kāi)啟內(nèi)存碎片整理過(guò)程(該過(guò)程無(wú)法并發(fā)负蚊,需要停頓)神妹。還可以使用參數(shù)-XX:CMSFullGCsBeforeCompaction
設(shè)定執(zhí)行多少次不壓縮的FullGC后,跟著來(lái)一次帶壓縮的(默認(rèn)為0家妆,即每次進(jìn)入FullGC時(shí)都進(jìn)行碎片壓縮)鸵荠。G1收集器
Garbage-First是當(dāng)今收集器技術(shù)發(fā)展的最前沿成果之一。
G1是一款面向服務(wù)器端應(yīng)用的垃圾收集器伤极,相比于CMS蛹找,優(yōu)點(diǎn)是:
①并行與并發(fā):縮短Stop The World的時(shí)間;
②分代收集:將堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域Region哨坪,雖然還保留新生代和老年代的概念熄赡,但新生代和老年代不再是物理隔離了,他們都是一部分Region的集合(不需要連續(xù))齿税;
③空間整合:從整體上看基于標(biāo)記-整理算法彼硫,從局部(兩個(gè)Region)看是基于復(fù)制算法,不會(huì)產(chǎn)生內(nèi)存碎片凌箕;
④可預(yù)測(cè)的停頓:可以指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間段內(nèi)拧篮,消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒。
內(nèi)存分配與回收策略
對(duì)象的內(nèi)存分配牵舱,就是在堆上分配串绩,對(duì)象主要分配在新生代的Eden區(qū)域,如果啟動(dòng)了本地線程分配緩沖芜壁,將按線程優(yōu)先在TLAB上分配礁凡。少數(shù)情況也可能會(huì)直接分配在老年代中。
分配的規(guī)則不是百分百固定的慧妄。其細(xì)節(jié)取決于當(dāng)前使用的是哪一種垃圾收集器組合顷牌,還有虛擬機(jī)中與內(nèi)存相關(guān)的參數(shù)的配置。
最普遍的內(nèi)存分配規(guī)則有以下幾點(diǎn):
對(duì)象優(yōu)先在Eden分配
大對(duì)象直接進(jìn)入老年代
通過(guò)設(shè)置參數(shù)-XX:PretenureSizeThreshold
參數(shù)塞淹,令大于該設(shè)置值的對(duì)象直接在老年代分配窟蓝。這樣做的目的是為了避免在Eden區(qū)和兩個(gè)Suvivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
虛擬機(jī)給每個(gè)對(duì)象定義一個(gè)對(duì)象年齡計(jì)數(shù)器(在對(duì)象頭中)饱普。如果對(duì)象在Eden出生并且經(jīng)過(guò)第一次Monior GC后仍然存活运挫,并且被Survivor容納的話状共,就將該對(duì)象的年齡設(shè)為1。對(duì)象在Survivor區(qū)中每熬過(guò)依次Monior GC谁帕,年齡就增加1峡继。當(dāng)該對(duì)象的年齡增加到一定程度(默認(rèn)為15),就會(huì)進(jìn)入老年代匈挖△尥郑可以通過(guò)參數(shù)-XX:MaxTenuringThreshold
設(shè)置年齡閾值。動(dòng)態(tài)對(duì)象年齡判定
如果在Survivor區(qū)間中相同年齡的所有對(duì)象大小的總和大于Survivor空間的一半关划,年齡>=該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)需等到參數(shù)-XX:MaxTenuringThreshold
設(shè)置的年齡翘瓮。空間分配擔(dān)保
在發(fā)生Monior GC之前贮折,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果大于资盅,則Monior GC可以確保是安全的调榄。如果小于,則虛擬機(jī)會(huì)查看HandlePromotionFailure
設(shè)置值是否允許擔(dān)保失敗呵扛,如果允許每庆,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于今穿,就嘗試進(jìn)行一次Monior GC(有風(fēng)險(xiǎn))缤灵,如果小于或者HandlePromotionFailure
設(shè)置不允許冒險(xiǎn),則此時(shí)改為進(jìn)行一次Full GC蓝晒。
概念補(bǔ)充
Minor GC:清理年輕代(包括 Eden 和 Survivor 區(qū)域)腮出,所有的 Minor GC 都會(huì)觸發(fā)stop-the-world。
Major GC:清理老年代芝薇。
Full GC:清理整個(gè)堆空間—包括年輕代和老年代胚嘲。
參考自:Minor GC、Major GC和Full GC之間的區(qū)別
內(nèi)容摘抄自《深入理解Java虛擬機(jī)》