一、GC的歷史
GC(Garbage Collection)垃圾收集,該技術(shù)并非Java語(yǔ)言的伴生技術(shù),GC的歷史比Java久遠(yuǎn)衔瓮,1960年誕生于MIT的Lisp是第一門真正使用內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)的語(yǔ)言。而Lisp在胚胎期時(shí)人們就在思考GC需要完成的事情抖甘。
二热鞍、GC需要完成的事情
- 那些內(nèi)存需要回收?
- 什么時(shí)候回收衔彻?
- 如何回收薇宠?
三、為何要了解GC和內(nèi)存分配
排查各種內(nèi)存溢出艰额、內(nèi)存泄漏問(wèn)題澄港,當(dāng)垃圾收集成為系統(tǒng)高并發(fā)的瓶頸時(shí)如何優(yōu)化。
四柄沮、Java虛擬機(jī)GC的主要區(qū)域
Java堆和方法區(qū)回梧。
五、如何判斷對(duì)象是否可回收
如何判斷對(duì)象是否可被回收
Java堆中存放的幾乎所有的對(duì)象實(shí)例铡溪,如何判斷那些對(duì)象還”存活“漂辐,那些已經(jīng)“死去”,這是進(jìn)行垃圾回收的關(guān)鍵棕硫。
判斷對(duì)象是否可被回收的算法有兩種:
1.引用計(jì)數(shù)算法
引用計(jì)數(shù)算法的原理:是通過(guò)在對(duì)象中添加一個(gè)引用計(jì)數(shù)器髓涯,通過(guò)對(duì)計(jì)數(shù)器的加減來(lái)記錄引用和引用失效,當(dāng)計(jì)數(shù)器歸零時(shí)就是指對(duì)象不能再被引用哈扮,引用計(jì)數(shù)算法實(shí)現(xiàn)簡(jiǎn)單纬纪,判定效率也很高,但是很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題滑肉,所以Java虛擬機(jī)中并沒有選用引用計(jì)數(shù)算法來(lái)管理內(nèi)存包各。
2.可達(dá)性分析算法
可達(dá)性分析算法的原理:是通過(guò)一系列稱為”GC Roots“的對(duì)象作為起始點(diǎn),從這些點(diǎn)出發(fā)開始向下搜索靶庙,搜索所走過(guò)的路徑稱為引用鏈问畅,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí)(即從根節(jié)點(diǎn)達(dá)到該對(duì)象是不可達(dá)的),則證明此對(duì)象是不可用的六荒。
GC Roots對(duì)象包括下面幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象护姆。
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象。
- 方法區(qū)中常用的引用對(duì)象掏击。
- 本地方法棧中JNI(即一般說(shuō)的Native方法)引用的對(duì)象
Java引用的概念:
- Java1.2之前Java對(duì)于引用的定義很傳統(tǒng):如果reference類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另一塊內(nèi)存的起始地址卵皂,就稱這塊內(nèi)存代表著一個(gè)引用。該定義下對(duì)象只有兩種狀態(tài):被引用或者沒有被引用兩種砚亭,對(duì)于一些當(dāng)內(nèi)存足夠時(shí)能保留灯变,不夠時(shí)則拋棄的對(duì)象無(wú)法描述殴玛,這類對(duì)象在很多有緩存功能的系統(tǒng)中都是存在的。
- Java1.2之后添祸,Java對(duì)引用的概念進(jìn)行了擴(kuò)充滚粟,將引用分為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)刃泌、弱引用(Weak Reference)坦刀、虛引用(Phantom Reference)四種,這四種引用的強(qiáng)度依次減弱蔬咬。
- 強(qiáng)引用就是指在程序之中普遍存在的鲤遥,類似“Object obj = new Object()”這類的引用,只要強(qiáng)引用還存在林艘,垃圾收集器永遠(yuǎn)不會(huì)回收調(diào)被引用的對(duì)象盖奈。
- 軟引用是用來(lái)描述一些還有用但非必需的對(duì)象。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象狐援,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前钢坦,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收。如果第二次回收還沒有足夠的內(nèi)存啥酱,這時(shí)會(huì)拋出內(nèi)存溢出異常爹凹。在JDK1.2之后,提供了SoftReference類來(lái)實(shí)現(xiàn)軟引用镶殷。
- 弱引用也是用來(lái)描述非必需對(duì)象的禾酱,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前绘趋。當(dāng)垃圾收集器工作時(shí)颤陶,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象陷遮。在JDK1.2之后滓走,提供WeakReference 類來(lái)實(shí)現(xiàn)弱引用。
- 虛引用也稱為幽靈引用或者幻影引用帽馋,它是最弱的一種引用關(guān)系搅方。一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響绽族,也無(wú)法通過(guò)虛引用來(lái)獲取一個(gè)對(duì)象實(shí)例姨涡。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。在JDK1.2之后项秉,提供了PhantomReference 類來(lái)實(shí)現(xiàn)虛引用绣溜。
五慷彤、如何回收對(duì)象(垃圾收集算法)
標(biāo)記-清除算法
“標(biāo)記-清除(Mark-Sweep)算法“是最基礎(chǔ)的收集算法娄蔼,算法分為兩個(gè)階段“標(biāo)記”和“清除”怖喻,標(biāo)記過(guò)程即為判斷對(duì)象是否存活的過(guò)程,清除為統(tǒng)一回收所被標(biāo)記的對(duì)象岁诉。之所以說(shuō)該算法是最基礎(chǔ)的算法锚沸,是因?yàn)楹罄m(xù)的收集算法都是在此基礎(chǔ)上針對(duì)其不足進(jìn)行改進(jìn)的。
不足:一是效率問(wèn)題涕癣,標(biāo)記和清除兩個(gè)過(guò)程的效率都不高哗蜈。二是空間問(wèn)題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片坠韩,空間碎片太多可能會(huì)導(dǎo)致以后運(yùn)行過(guò)程中分配較大的對(duì)象時(shí)距潘,無(wú)法找到足夠連續(xù)的內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。
復(fù)制算法
將內(nèi)存按容量劃分為大小相等的兩塊只搁,每次只使用其中一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活的對(duì)象復(fù)制到另外一塊上面幽勒,然后再把已使用過(guò)的內(nèi)存空間一次清理掉座舍。
優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行效率高焰望。每次都對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收骚亿,內(nèi)存分配時(shí)不用考慮內(nèi)存碎片等復(fù)雜情況。
缺點(diǎn):可用內(nèi)存變成一半熊赖,內(nèi)存浪費(fèi)嚴(yán)重来屠,代價(jià)太大。
優(yōu)化:通過(guò)IBM公司專門的研究表明震鹉,新生代中的對(duì)象98%是“朝生夕死”的的妖,所以并不需要按照1:1的比例來(lái)劃分內(nèi)存空間。將內(nèi)存分為三塊一塊較大的Eden空間和兩塊較小的Survivor空間足陨,每次使用兩塊即一塊Eden和一塊Survivor嫂粟,當(dāng)回收時(shí),將Eden和Survivor中還存活的對(duì)象一次性復(fù)制到另一塊Survivor空間上墨缘,最后清理掉Eden和剛才使用過(guò)的Survivor空間星虹。HotSpot虛擬機(jī)默認(rèn)Eden和Survivor大小比例是8:1,也就是每次新生代可用內(nèi)存空間為整個(gè)新生代的90%(80%+10%)镊讼,只有10%的內(nèi)存會(huì)被浪費(fèi)宽涌。(當(dāng)一次垃圾收集存活的對(duì)象占用的空間大于10%時(shí),則需要由其他內(nèi)存(指老年代)進(jìn)行分配擔(dān)保(Handle Promotion))
標(biāo)記-整理算法
同“標(biāo)記-清理”算法一樣“標(biāo)記-整理”(Mark-Compact)算法也分為兩個(gè)階段“標(biāo)記”和“整理”蝶棋,標(biāo)記過(guò)程即為判斷對(duì)象是否存活的過(guò)程卸亮,整理過(guò)程不是在直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng)玩裙。然后直接清理掉端邊界以外的內(nèi)存兼贸。
適用范圍:老年代
分代收集算法
”分代收集“(Generational Collection)算法段直,根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊。一般是把Java堆分為新生代和老年代溶诞。
優(yōu)點(diǎn):可以根據(jù)各個(gè)年代的特點(diǎn)采用最合適的收集算法鸯檬。新生代中,每次垃圾回收時(shí)都會(huì)有大量的對(duì)象死去螺垢,只有少量存活喧务,那就選用復(fù)制算法,只需要少量存活對(duì)象的復(fù)制成本就可以完成收集枉圃。老年代中因?yàn)閷?duì)象存活率高功茴、沒有額外的空間對(duì)它進(jìn)行分配擔(dān)保,必須使用“標(biāo)記-清理”或者“標(biāo)記-整理”算法來(lái)進(jìn)行回收孽亲。
HotSpot垃圾收集器
Serial收集器
Serial收集器是最基本痊土、發(fā)展歷史最悠久的收集器,曾經(jīng)(在JDK1.3.1之前)是虛擬機(jī)新生代收集的唯一選擇墨林。Serial收集器是一個(gè)單線程的收集器赁酝,它只會(huì)使用一個(gè)cpu或一條收集線程去完成收集工作,而且在進(jìn)行垃圾收集時(shí)旭等,必須暫停其他所有的工作線程(即虛擬機(jī)中的Stop The World)酌呆,直到垃圾收集結(jié)束。
ParNew收集器
ParNew收集器其實(shí)就是Serial收集器的多線程版本搔耕,除了使用多條線程進(jìn)行垃圾收集之外隙袁,其余行為包括Serial收集器可用的控制參數(shù)、收集算法弃榨、Stop The World菩收、對(duì)象分配規(guī)則、回收策略等都與Serial收集器完全一樣鲸睛。
ParNew收集器在單CPU的環(huán)境中絕對(duì)不會(huì)比Serial收集器有更好的效果娜饵,甚至存在線程交互的開銷,該收集器在通過(guò)超線程技術(shù)實(shí)現(xiàn)的兩個(gè)CPU的環(huán)境中都不能百分百地保證可以超越Serial收集器官辈,但是隨著CPU的數(shù)量增加箱舞,GC時(shí)系統(tǒng)資源的有效利用率提高
Parallel Scavenge收集器
Parallel Scavenge收集器是一個(gè)新生代收集器,使用的是復(fù)制算法拳亿,并且是多線程的收集器晴股。Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間肺魁,而Parallel Scavenge收集器的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量(Throughput)电湘。所謂吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間)。
Serial Old收集器
Serial Old 是Serial收集器的老年代版本寂呛,它是一個(gè)單線程收集器怎诫,使用“標(biāo)記-整理”算法。這個(gè)收集器的主要意義也是在于給Client模式下的虛擬機(jī)使用昧谊。如果在Server模式下,那么它主要還用兩大用途:一是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用酗捌,二是作為CMS收集器的后備預(yù)案呢诬,在并發(fā)收集發(fā)生Concurrent ModeFailure時(shí)使用。
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本胖缤,使用對(duì)線程和“標(biāo)記-整理”算法尚镰。這個(gè)收集器是在JDK1.6中才開始提供的,在此之前狗唉,新生代的Parallel Scavenge收集器一直處于比較尷尬的狀態(tài),原因是分俯,如果選擇了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器外別無(wú)選擇缸剪,由于老年代Serial Old收集器在服務(wù)端應(yīng)用性能上的“拖累”,使用了Parllel Scavenge收集器也未必在整體性能上獲得吞吐量最大化的效果东亦,由于單線程的老年代集中無(wú)法充分利用服務(wù)器多CPU的處理能力杏节,在老年代很大而且硬件比較高級(jí)的環(huán)境中,這種組合的吞吐量甚至還不一定有ParNew加CMS的組合“給力”典阵。在注重吞吐量以及CPU資源敏感的場(chǎng)合奋渔,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器壮啊。目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)器上嫉鲸,這類應(yīng)用尤其重視服務(wù)器的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間最短歹啼,以給用戶帶來(lái)較好的體驗(yàn)充坑。CMS收集器就非常符合這類應(yīng)用的需求。
CMS收集器是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的染突,運(yùn)作過(guò)程相對(duì)于前面幾種收集器來(lái)說(shuō)更為復(fù)雜一些捻爷,整個(gè)過(guò)程分為四步:
- 初始標(biāo)記(CMS initial mark)
- 并發(fā)標(biāo)記(CMS concurrent mark)
- 重新標(biāo)記(CMS remark)
-
并發(fā)清除(CMS concurrent sweep)
其中,初始標(biāo)記份企、重新標(biāo)記這兩個(gè)步驟仍然需要“Stop The World”也榄。初始標(biāo)記僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快,并發(fā)標(biāo)記階段就是進(jìn)行GC Roots Tracing過(guò)程甜紫,而重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄降宅,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段稍長(zhǎng)一些,但遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短囚霸。
由于整個(gè)過(guò)程中耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除過(guò)程收集器線程都可以于用戶線程一起工作腰根,所以,總體來(lái)說(shuō)拓型,CMS收集器的內(nèi)存回收是與用戶線程一起并發(fā)執(zhí)行的
CMS三個(gè)明顯缺點(diǎn):
- CMS收集器對(duì)CPU資源非常敏感额嘿。
- CMS收集器無(wú)法處理浮動(dòng)垃圾,可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生。
- CMS是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的收集器劣挫,收集結(jié)束后會(huì)產(chǎn)生大量空間碎片册养。
G1
G1(Garbage-First)收集器是當(dāng)今收集器技術(shù)發(fā)展的最前沿成果之一,早在JDK1.7剛剛確立項(xiàng)目目標(biāo)压固,Sun公司給出的JDK1.7 RoadMap里面球拦,它就被視為JDK 1.7 中HotSpot虛擬機(jī)的一個(gè)重要進(jìn)化特征坎炼,在JDK7u4時(shí)Sun公司認(rèn)為它達(dá)到足夠成熟的商用程度点弯,移除了“Experimental”的標(biāo)識(shí)抢肛。
G1是一款面向服務(wù)器應(yīng)用的垃圾收集器捡絮。HotSpot開發(fā)團(tuán)隊(duì)賦予它的使命是(在比較長(zhǎng)期的)未來(lái)可以替換掉JDK1.5發(fā)布的CMS收集器福稳。與其他GC收集器相比的圆,G1具備如下特點(diǎn):
- 并行與并發(fā):G1能充分利用多CPU半火、多核環(huán)境下的硬件優(yōu)勢(shì)钮糖,使用多個(gè)CPU(CPU或者CPU核心)來(lái)縮短Stop-The-World停頓的時(shí)間,部分其他收集器原本需要停頓Java線程執(zhí)行的GC動(dòng)作阎抒,G1收集器仍然可以通過(guò)并發(fā)的方式讓Java程序繼續(xù)執(zhí)行且叁。
- 分代收集:與其他收集器一樣逞带,分代概念在G1中依然得以保留。雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個(gè)GC堆掰担,但它能采用不同的方式去處理新創(chuàng)建的對(duì)象和已經(jīng)存活了一段時(shí)間带饱、熬過(guò)多次GC的舊對(duì)象以獲取更好的收集效果勺疼。
- 空間整合:與CMS的“標(biāo)記-清理”算法不同执庐,G1從整體來(lái)看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器轨淌,從局部(兩個(gè)Region之間)上來(lái)看是基于“復(fù)制”算法實(shí)現(xiàn)的递鹉,但無(wú)論如何躏结,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片媳拴,收集后能提供規(guī)整的可用內(nèi)存屈溉。這種特性有利于程序長(zhǎng)時(shí)間運(yùn)作语婴,分配大對(duì)象時(shí)不會(huì)因?yàn)闊o(wú)法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC砰左。
-
可預(yù)測(cè)的停頓:這是G1相對(duì)于CMS的另一大優(yōu)勢(shì)廉羔,降低停頓時(shí)間是G1和CMS共同的關(guān)注點(diǎn)僻造,但G1除了追求低停頓外髓削,還能建立可預(yù)測(cè)的挺堵時(shí)間模型立膛,能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi)宝泵,消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒,這幾乎已經(jīng)是實(shí)時(shí)Java(RTSJ)的垃圾收集器的特征了框往。
在G1之前的其他收集器進(jìn)行收集的范圍都是整個(gè)新生代或者老年代椰弊,而G1不在是這樣秉版。使用G1收集器時(shí)沐飘,Java堆的內(nèi)存布局就與其他收集器有很大差別耐朴,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)筛峭,雖然還保留有新生代和老年代的概念镰吵,但新生代和老年代不在是物理隔離的挂签,它們都是一部分Region(不需要連續(xù))的集合勺馆。
G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗梢杂杏?jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集,G1跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大小(回收所獲的空間大小以及回收所需要時(shí)間的經(jīng)驗(yàn)值)怀樟,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表共耍,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region(這也就是Garbage-First名稱的由來(lái))遗淳。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式脂男,保證了G1收集器在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率。
“化整為零”思路存在的問(wèn)題及解決辦法:
將Java堆化整為零后可以Region為單位進(jìn)行垃圾收集,但是由于Region不是孤立的掉缺,一個(gè)對(duì)象分配在某個(gè)Region中筐高,它并非只能被本Region中的其他對(duì)象以你用蜀肘,二是可以與整個(gè)Java堆任意的對(duì)象發(fā)生引用關(guān)系狐榔。那在做可達(dá)性判定確定對(duì)象是否存活時(shí)就要對(duì)整個(gè)堆進(jìn)行掃描才能確保準(zhǔn)確性庵楷。為了解決這個(gè)問(wèn)題,G1中每個(gè)Region都有一個(gè)與之對(duì)應(yīng)的Remembered Set挎春,虛擬機(jī)發(fā)現(xiàn)程序在對(duì)Reference類型的數(shù)據(jù)進(jìn)行寫操作時(shí),會(huì)產(chǎn)生一個(gè)Write Barrier暫時(shí)中斷寫操作,檢查Reference引用的對(duì)象是否處于不同的Region之中,如果是梆惯,便通過(guò)CardTable把相關(guān)引用信息記錄到被引用對(duì)象所屬的Region的Remembered Set之中。當(dāng)進(jìn)行內(nèi)存回收時(shí),在GC根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set即可保證不對(duì)全堆掃描也不會(huì)有遺漏
(該問(wèn)題并不是G1中才有的蚀之,在以前的分代收集中也是存在的,只是在G1中更加突出了,以前分代收集新生代時(shí)也是不得不對(duì)老年代進(jìn)行掃描咏瑟,解決辦法也是引入Remembered Set)
如果不計(jì)算維護(hù)Remembered Set 的操作狼犯,G1收集器的運(yùn)作大致可以劃分以下幾個(gè)步驟:
- 初始標(biāo)記(Initial Marking):僅僅是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象领铐,并且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發(fā)運(yùn)行時(shí)宋舷,能在正確可用的Region中創(chuàng)建新對(duì)象绪撵,這階段需要停頓線程祝蝠,但耗時(shí)很短绎狭。
- 并發(fā)標(biāo)記(Concurrent Marking):是從GC Roots開始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,找出存活的對(duì)象社付,這一階段耗時(shí)較長(zhǎng)燕鸽,但可與用戶程序并發(fā)執(zhí)行。
- 最終標(biāo)記(Final Marking):是為了修正在并發(fā)階段因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄啊研,虛擬機(jī)將這段時(shí)間對(duì)象變化記錄在線程Remembered Set Logs里面沟娱,最終標(biāo)記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中盏缤,該階段需要停頓線程潭流,但是可并行執(zhí)行。
- 篩選回收(Live Data Counting andEvacuation):首先對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時(shí)間來(lái)制定回收計(jì)劃贡这,從Sun公司透露出來(lái)的信息來(lái)看辈双,這個(gè)階段其實(shí)也可以做到與用戶程序一起并發(fā)執(zhí)行证芭,但是因?yàn)橹换厥找徊糠諶egion湃密,時(shí)間是用戶可控的,而且停頓用戶線程將大幅度提高收集效率缎玫。
六、對(duì)象的分配策略
對(duì)象的內(nèi)存分配,往大方向講使碾,就是在堆上分配(但也可能經(jīng)過(guò)JIT編譯后被拆散為標(biāo)量類型并間接地棧上分配)掐禁,對(duì)象主要分配在新生代的Eden區(qū)上,如果啟動(dòng)了本地線程分配緩沖,將按線程優(yōu)先在TLAB上分配。少數(shù)情況下也可能會(huì)直接分配在老年代中,分配的規(guī)則并不是百分比固定的脱柱,其細(xì)節(jié)取決于當(dāng)前使用的是哪一種垃圾收集器組合昧狮,還用虛擬機(jī)中與內(nèi)存相關(guān)的參數(shù)的設(shè)置笨使。
對(duì)象優(yōu)先在Eden分配
大多數(shù)情況下奕翔,對(duì)象在新生代Eden區(qū)中分配驾窟。當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時(shí)昨稼,虛擬機(jī)將發(fā)起一次MinorGC
虛擬機(jī)提供了-XX:+PrintGCDetails這個(gè)收集器日志參數(shù)寻行,告訴虛擬機(jī)在發(fā)生垃圾收集行為時(shí)打印內(nèi)存回收日志拌蜘,并且在進(jìn)程退出的時(shí)候輸出當(dāng)前的內(nèi)存各區(qū)域分配情況铜涉。
??Minor GC和Full GC的區(qū)別:
- 新生代GC(Minor GC):指發(fā)生在新生代的垃圾收集動(dòng)作,因?yàn)镴ava對(duì)象大多都具備朝生夕死的特性贱鼻,所以Minor GC非常頻繁,一般回收速度也比較快
- 老年代GC(Major GC/Full GC):指發(fā)生在老年代的GC攘烛,出現(xiàn)了Major GC魏滚,經(jīng)常會(huì)伴隨至少一次的Minor GC(但非絕對(duì)的,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過(guò)程)坟漱。Major GC的速度一般會(huì)比Minor GC慢10倍以上鼠次。
大對(duì)象直接進(jìn)入老年代
所謂的大對(duì)象是指,需要大量連續(xù)內(nèi)存空間的Java對(duì)象芋齿,最經(jīng)典的大對(duì)象就是那種很長(zhǎng)的字符串以及數(shù)組腥寇。大對(duì)象對(duì)虛擬機(jī)的內(nèi)存分配來(lái)說(shuō)就是一個(gè)壞消息(比遇到一個(gè)大對(duì)象更壞的是遇到一群“朝生夕死”的“大對(duì)象”,寫程序時(shí)應(yīng)當(dāng)避免)觅捆,經(jīng)常出現(xiàn)大對(duì)象容易導(dǎo)致內(nèi)存還有不少空間時(shí)就提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間來(lái)“安置”它們赦役。
虛擬機(jī)提供了一個(gè)-XX:PretenureSizeThreshold參數(shù)(該參數(shù)只對(duì)Serial 和ParNew兩款收集器有效),令大于這個(gè)設(shè)置值的對(duì)象直接在老年代分配栅炒。這樣做的目的是避免在EDen區(qū)及兩個(gè)Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制掂摔。
動(dòng)態(tài)對(duì)象年齡判定
為了能更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshold才能晉級(jí)老年代职辅,如果在Survivor空間中相同年齡獸所有對(duì)象大小的總和大于Survivor空間的一半棒呛,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須等到MaxTenuringThreshold中要求的年齡域携。
空間分配擔(dān)保
虛擬機(jī)在發(fā)生MinorGC之前簇秒,會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立秀鞭,那么Minor GC可以確保是安全的趋观。如果不成立,則虛擬機(jī)會(huì)查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗锋边。如果允許皱坛,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于豆巨,將嘗試著進(jìn)行一次Minor GC盡管這次Minor GC是有風(fēng)險(xiǎn)的剩辟;如果小于,或者HandlePromotionFailure設(shè)置不允許冒險(xiǎn)往扔,那這時(shí)要改為進(jìn)行一次Full GC贩猎。
《深入理解Java虛擬機(jī) JVM高級(jí)特性與最佳實(shí)踐 第二版》