JVM內(nèi)存管理之堆

JVM規(guī)范沒有規(guī)定堆在物理上一定連續(xù)猴誊,但是實(shí)現(xiàn)一般是連續(xù)的(比如HtotSpot)。

1. 對象的創(chuàng)建過程

1 檢查是否能在常量池中定位到一個類的符號引用及汉,并且檢查這個符號引用代表的類是否已被加載霹琼、解析和初始化過路翻。如果沒有跪但,進(jìn)行加載履羞、解析和初始化。
2 內(nèi)存分配屡久。類確定之后忆首,對象的內(nèi)存就確定了。
內(nèi)存分配有兩種辦法:

  • 指針碰撞(serial涂身、ParNew)
  • 空閑列表法(CMS)

內(nèi)存分配中的競爭:

  • CAS
  • TLAB(Thread Local Allocation Buffer) -XX:+/-UseTLAB

3 初始化為零值
如果使用額TLAB,該步可以提前至分配TLAB中進(jìn)行搓蚪。
4 設(shè)置對象頭
5 init

2. 對象的內(nèi)存布局

對象由對象頭蛤售、實(shí)例數(shù)據(jù)和對齊填充組成。
1 對象頭(32位計(jì)算機(jī)為32位妒潭,64位計(jì)算機(jī)為64位)

  • 哈希碼悴能、GC分代年齡、鎖標(biāo)志雳灾、線程持有的鎖漠酿、偏向線程ID、偏向時間戳等谎亩。* 類型指針炒嘲。通過類型指針可以確定屬于哪個類。
  • 如果是數(shù)組匈庭,對象頭記錄了數(shù)組的大小夫凸。
  1. 實(shí)例數(shù)據(jù)
    • 相同寬度的數(shù)據(jù)總是被分配在一起。
    • 在父類中定義的變量總是在子類前面阱持。
  2. 填充(32位計(jì)算機(jī)為32位夭拌,64位計(jì)算機(jī)為64位)

3. 對象的訪問定位

  1. 句柄
    reference中存儲的是規(guī)定的句柄地址,在對象唄移動時只會改變句柄中的實(shí)例數(shù)據(jù)指針,而reference不需要修改
  2. 直接指針
    減少了一次內(nèi)存訪問鸽扁,hotspot采取該方式蒜绽。

4. 垃圾收集

4.1 分代收集

根據(jù)對象的生命周期的特點(diǎn),將對象進(jìn)行分代桶现,堆的內(nèi)存管理和垃圾收集也根據(jù)分代的不同采取不同的策略躲雅。分代主要基于下面兩個原因:

  • 大多數(shù)分配對象的存活時間很短(98%)
  • 存活時間久的對象很少引用存活時間短的對象。
  1. 新生代
    • 頻率高
    • 時間短
    • 空間利用率低
  2. 老年代
    • 頻率低
    • 時間長
    • 空間利用率高
  3. 永久代

垃圾收集器不需要掃描整個老年代就能識別整個新生代中存活的對象巩那,從而縮短mirror gc的時間吏夯。主要利用了卡表。

4.2 新生代

新生代采取復(fù)制算法即横,分為Eden區(qū)噪生、2個survivor區(qū),比例默認(rèn)為8:1:1东囚。

  1. 新創(chuàng)建的對象在Eden創(chuàng)建跺嗽,如果Eden區(qū)空間不夠,發(fā)起mirror gc页藻。Eden區(qū)的對象的年齡都是0桨嫁。Eden區(qū)的內(nèi)存分配采取指針碰撞,使用TLAB減小競爭份帐。
  2. 大對象直接在老年代創(chuàng)建
    -XX:PretenureSizeThreshold
    原因:
    • 容易引起mirror gc(Eden很快就滿了)
    • 在mirror gc時可能復(fù)制大量對象璃吧,而且survivor區(qū)可能還容納不了gc后的對象,進(jìn)而引發(fā)老年代擔(dān)保废境。
  3. GC時將Eden區(qū)和已使用的Survivor區(qū)的對象復(fù)制未使用的Survivor區(qū)對象畜挨。已使用Survivor區(qū)的某些對象的年齡可能達(dá)到了老年代年齡,會進(jìn)入老年代(-XX:MaxTenuringThreshold)噩凹。
  4. 空間分配擔(dān)保巴元。
  • 1.6之前,判斷年輕代的大小是否小于老年代可容納大小驮宴,如果是則認(rèn)為yang gc是安全的逮刨,發(fā)起yang gc。否則堵泽,判斷HandlePromotionFailure參數(shù)是否為true修己,如果為true,判斷統(tǒng)計(jì)的晉升到老年代的大小是否小于老年代可容納大小迎罗,如果是的則發(fā)起yang gc箩退,否則發(fā)起 full gc。
  • 1.6之后佳谦,HandlePromotionFailure失效戴涝。過程和HandlePromotionFailure=true一樣。
  1. 動態(tài)對象年齡判定
    如果在survivor空間中相同年齡所有對象大小的總和大于survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代啥刻,無需等到MaxTenuringThreshold中要求的年齡奸鸯。

4.3 七種垃圾收集器

如果說收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)可帽。Java虛擬機(jī)規(guī)范中對垃圾收集器應(yīng)該如何實(shí)現(xiàn)并沒有任何規(guī)定娄涩,因此不同的廠商、版本的虛擬機(jī)所提供的垃圾收集器都可能會有很大差別映跟,并且一般都會提供參數(shù)供用戶根據(jù)自己的應(yīng)用特點(diǎn)和要求組合出各個年代所使用的收集器蓄拣。接下來討論的收集器基于JDK1.7 Update 14 之后的HotSpot虛擬機(jī)(在此版本中正式提供了商用的G1收集器,之前G1仍處于實(shí)驗(yàn)狀態(tài))努隙,該虛擬機(jī)包含的所有收集器如下圖所示:

image

上圖展示了7種作用于不同分代的收集器球恤,如果兩個收集器之間存在連線,就說明它們可以搭配使用荸镊。虛擬機(jī)所處的區(qū)域咽斧,則表示它是屬于新生代收集器還是老年代收集器。Hotspot實(shí)現(xiàn)了如此多的收集器躬存,正是因?yàn)槟壳安o完美的收集器出現(xiàn)张惹,只是選擇對具體應(yīng)用最適合的收集器。

4.3.1 相關(guān)概念

4.3.1.1并行和并發(fā)

  • 并行(Parallel):指多條垃圾收集線程并行工作岭洲,但此時用戶線程仍然處于等待狀態(tài)宛逗。
  • 并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時執(zhí)行(但不一定是并行的,可能會交替執(zhí)行)盾剩,用戶程序在繼續(xù)運(yùn)行雷激。而垃圾收集程序運(yùn)行在另一個CPU上。

4.3.1.2 吞吐量(Throughput)

吞吐量就是CPU用于運(yùn)行用戶代碼的時間CPU總消耗時間的比值彪腔,即

吞吐量 = 運(yùn)行用戶代碼時間 /(運(yùn)行用戶代碼時間 + 垃圾收集時間)侥锦。

假設(shè)虛擬機(jī)總共運(yùn)行了100分鐘进栽,其中垃圾收集花掉1分鐘德挣,那吞吐量就是99%。

4.3.1.3 Minor GC 和 Full GC

  • 新生代GC(Minor GC):指發(fā)生在新生代的垃圾收集動作快毛,因?yàn)镴ava對象大多都具備朝生夕滅的特性格嗅,所以Minor GC非常頻繁,一般回收速度也比較快唠帝。具體原理見上一篇文章屯掖。
  • 老年代GC(Major GC / Full GC):指發(fā)生在老年代的GC,出現(xiàn)了Major GC襟衰,經(jīng)常會伴隨至少一次的Minor GC(但非絕對的贴铜,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。

4.3.2 新生代收集器

4.3.2.1 Serial收集器

Serial(串行)收集器是最基本绍坝、發(fā)展歷史最悠久的收集器徘意,它是采用復(fù)制算法新生代收集器,曾經(jīng)(JDK 1.3.1之前)是虛擬機(jī)新生代收集的唯一選擇轩褐。它是一個單線程收集器椎咧,只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是它在進(jìn)行垃圾收集時把介,必須暫停其他所有的工作線程勤讽,直至Serial收集器收集結(jié)束為止(“Stop The World”)。這項(xiàng)工作是由虛擬機(jī)在后臺自動發(fā)起和自動完成的拗踢,在用戶不可見的情況下把用戶正常工作的線程全部停掉脚牍,這對很多應(yīng)用來說是難以接收的。

下圖展示了Serial 收集器(老年代采用Serial Old收集器)的運(yùn)行過程:

image

為了消除或減少工作線程因內(nèi)存回收而導(dǎo)致的停頓秒拔,HotSpot虛擬機(jī)開發(fā)團(tuán)隊(duì)在JDK 1.3之后的Java發(fā)展歷程中研發(fā)出了各種其他的優(yōu)秀收集器莫矗,這些將在稍后介紹。但是這些收集器的誕生并不意味著Serial收集器已經(jīng)“老而無用”砂缩,實(shí)際上到現(xiàn)在為止作谚,它依然是HotSpot虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)的新生代收集器。它也有著優(yōu)于其他收集器的地方:簡單而高效(與其他收集器的單線程相比)庵芭,對于限定單個CPU的環(huán)境來說妹懒,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得更高的單線程收集效率双吆。

在用戶的桌面應(yīng)用場景中眨唬,分配給虛擬機(jī)管理的內(nèi)存一般不會很大,收集幾十兆甚至一兩百兆的新生代(僅僅是新生代使用的內(nèi)存好乐,桌面應(yīng)用基本不會再大了)匾竿,停頓時間完全可以控制在幾十毫秒最多一百毫秒以內(nèi),只要不頻繁發(fā)生蔚万,這點(diǎn)停頓時間可以接收岭妖。所以,Serial收集器對于運(yùn)行在Client模式下的虛擬機(jī)來說是一個很好的選擇反璃。

4.3.2.2 ParNew 收集器

ParNew收集器就是Serial收集器的多線程版本昵慌,它也是一個新生代收集器。除了使用多線程進(jìn)行垃圾收集外淮蜈,其余行為包括Serial收集器可用的所有控制參數(shù)斋攀、收集算法(復(fù)制算法)、Stop The World梧田、對象分配規(guī)則淳蔼、回收策略等與Serial收集器完全相同侧蘸,兩者共用了相當(dāng)多的代碼。

ParNew收集器的工作過程如下圖(老年代采用Serial Old收集器):

image

ParNew收集器除了使用多線程收集外鹉梨,其他與Serial收集器相比并無太多創(chuàng)新之處闺魏,但它卻是許多運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器,其中有一個與性能無關(guān)的重要原因是俯画,除了Serial收集器外析桥,目前只有它能和CMS收集器(Concurrent Mark Sweep)配合工作,CMS收集器是JDK 1.5推出的一個具有劃時代意義的收集器艰垂,具體內(nèi)容將在稍后進(jìn)行介紹泡仗。

ParNew 收集器在單CPU的環(huán)境中絕對不會有比Serial收集器有更好的效果,甚至由于存在線程交互的開銷猜憎,該收集器在通過超線程技術(shù)實(shí)現(xiàn)的兩個CPU的環(huán)境中都不能百分之百地保證可以超越娩怎。多CPU環(huán)境下,隨著CPU的數(shù)量增加胰柑,它對于GC時系統(tǒng)資源的有效利用是很有好處的截亦。它默認(rèn)開啟的收集線程數(shù)與CPU的數(shù)量相同柬讨,在CPU非常多的情況下可使用-XX:ParallerGCThreads參數(shù)設(shè)置崩瓤。

4.3.2.3 Parallel Scavenge 收集器

Parallel Scavenge收集器也是一個并行多線程新生代收集器黔攒,它也使用復(fù)制算法趁啸。Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能縮短垃圾收集時用戶線程的停頓時間亏钩,而Parallel Scavenge收集器的目標(biāo)是達(dá)到一個可控制的吞吐量(Throughput)莲绰。

停頓時間越短就越適合需要與用戶交互的程序欺旧,良好的響應(yīng)速度能提升用戶體驗(yàn)姑丑。而高吞吐量則可以高效率地利用CPU時間,盡快完成程序的運(yùn)算任務(wù)辞友,主要適合在后臺運(yùn)算而不需要太多交互的任務(wù)栅哀。

Parallel Scavenge收集器除了會顯而易見地提供可以精確控制吞吐量的參數(shù)震肮,還提供了一個參數(shù)-XX:+UseAdaptiveSizePolicy,這是一個開關(guān)參數(shù)留拾,打開參數(shù)后戳晌,就不需要手工指定新生代的大小(-Xmn)痴柔、Eden和Survivor區(qū)的比例(-XX:SurvivorRatio)沦偎、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù)了,虛擬機(jī)會根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息咳蔚,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時間或者最大的吞吐量豪嚎,這種方式稱為GC自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics)自適應(yīng)調(diào)節(jié)策略也是Parallel Scavenge收集器與ParNew收集器的一個重要區(qū)別谈火。

另外值得注意的一點(diǎn)是侈询,Parallel Scavenge收集器無法與CMS收集器配合使用,所以在JDK 1.6推出Parallel Old之前糯耍,如果新生代選擇Parallel Scavenge收集器扔字,老年代只有Serial Old收集器能與之配合使用。

4.3.3 老年代收集器

4.3.3.1 Serial Old收集器

Serial Old 是 Serial收集器的老年代版本温技,它同樣是一個單線程收集器革为,使用“標(biāo)記-整理”(Mark-Compact)算法。

此收集器的主要意義也是在于給Client模式下的虛擬機(jī)使用舵鳞。如果在Server模式下篷角,它還有兩大用途:

  • 在JDK1.5 以及之前版本(Parallel Old誕生以前)中與Parallel Scavenge收集器搭配使用。
  • 作為CMS收集器的后備預(yù)案系任,在并發(fā)收集發(fā)生Concurrent Mode Failure時使用恳蹲。

它的工作流程與Serial收集器相同,這里再次給出Serial/Serial Old配合使用的工作流程圖:


image

4.3.3.2 Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本俩滥,使用多線程“標(biāo)記-整理”算法嘉蕾。前面已經(jīng)提到過,這個收集器是在JDK 1.6中才開始提供的霜旧,在此之前错忱,如果新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old以外別無選擇挂据,所以在Parallel Old誕生以后以清,“吞吐量優(yōu)先”收集器終于有了比較名副其實(shí)的應(yīng)用組合,在注重吞吐量以及CPU資源敏感的場合崎逃,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器掷倔。Parallel Old收集器的工作流程與Parallel Scavenge相同,這里給出Parallel Scavenge/Parallel Old收集器配合使用的流程圖:

image

4.3.3.3 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器个绍,它非常符合那些集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端上的Java應(yīng)用勒葱,這些應(yīng)用都非常重視服務(wù)的響應(yīng)速度浪汪。從名字上(“Mark Sweep”)就可以看出它是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的。

CMS收集器工作的整個流程分為以下4個步驟:

  • 初始標(biāo)記(CMS initial mark):僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象凛虽,速度很快死遭,需要“Stop The World”。
  • 并發(fā)標(biāo)記(CMS concurrent mark)/預(yù)清除:進(jìn)行GC Roots Tracing的過程凯旋,在整個過程中耗時最長呀潭。
    完成一些要在重新標(biāo)記階段完成的工作,即重新遍歷那些在標(biāo)記期間因并發(fā)而被改掉的對象至非。主要是減小重新標(biāo)記階段的工作量蜗侈,從而減小停頓。
  1. 重新標(biāo)記(停頓睡蟋、多線程)
  • 重新標(biāo)記(CMS remark):為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄踏幻,這個階段的停頓時間一般會比初始標(biāo)記階段稍長一些,但遠(yuǎn)比并發(fā)標(biāo)記的時間短戳杀。此階段也需要“Stop The World”该面。
  • 并發(fā)清除(CMS concurrent sweep)

由于整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作,所以信卡,從總體上來說隔缀,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的。通過下圖可以比較清楚地看到CMS收集器的運(yùn)作步驟中并發(fā)和需要停頓的時間:

image

優(yōu)點(diǎn)

CMS是一款優(yōu)秀的收集器傍菇,它的主要優(yōu)點(diǎn)在名字上已經(jīng)體現(xiàn)出來了:并發(fā)收集猾瘸、低停頓,因此CMS收集器也被稱為并發(fā)低停頓收集器(Concurrent Low Pause Collector)丢习。
缺點(diǎn)

  • 對CPU資源非常敏感(多線程GC都會造成load高)牵触,吞吐量降低 其實(shí),面向并發(fā)設(shè)計(jì)的程序都對CPU資源比較敏感咐低。在并發(fā)階段揽思,它雖然不會導(dǎo)致用戶線程停頓,但會因?yàn)檎加昧艘徊糠志€程(或者說CPU資源)而導(dǎo)致應(yīng)用程序變慢见擦,總吞吐量會降低钉汗。CMS默認(rèn)啟動的回收線程數(shù)是(CPU數(shù)量+3)/4,也就是當(dāng)CPU在4個以上時鲤屡,并發(fā)回收時垃圾收集線程不少于25%的CPU資源损痰,并且隨著CPU數(shù)量的增加而下降。但是當(dāng)CPU不足4個時(比如2個)酒来,CMS對用戶程序的影響就可能變得很大卢未,如果本來CPU負(fù)載就比較大,還要分出一半的運(yùn)算能力去執(zhí)行收集器線程役首,就可能導(dǎo)致用戶程序的執(zhí)行速度忽然降低了50%尝丐,其實(shí)也讓人無法接受。
  • 要求更大的老年代內(nèi)存衡奥。
    1 在GC過程中爹袁,在第四階段才真正的回收內(nèi)存。
    2 標(biāo)記-清除內(nèi)存管理造成碎片矮固。
    3 浮動垃圾
    由于CMS并發(fā)清理階段用戶線程還在運(yùn)行著失息,伴隨程序運(yùn)行自然就還會有新的垃圾不斷產(chǎn)生。這一部分垃圾出現(xiàn)在標(biāo)記過程之后档址,CMS無法再當(dāng)次收集中處理掉它們盹兢,只好留待下一次GC時再清理掉。這一部分垃圾就被稱為“浮動垃圾”守伸。也是由于在垃圾收集階段用戶線程還需要運(yùn)行绎秒,那也就還需要預(yù)留有足夠的內(nèi)存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進(jìn)行收集尼摹,需要預(yù)留一部分空間提供并發(fā)收集時的程序運(yùn)作使用见芹。
  • 可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生,此時會采取Serial old的方式進(jìn)行GC蠢涝。
  • mirror gc有所延長玄呛。mirror gc時會造成年輕代對象升級到老年代,而老年代采取的是空閑列表法(分配內(nèi)存效率低)和二。

4.3.4 G1收集器

G1(Garbage-First)收集器是當(dāng)今收集器技術(shù)發(fā)展最前沿的成果之一徘铝,它是一款面向服務(wù)端應(yīng)用的垃圾收集器,HotSpot開發(fā)團(tuán)隊(duì)賦予它的使命是(在比較長期的)未來可以替換掉JDK 1.5中發(fā)布的CMS收集器惯吕。與其他GC收集器相比惕它,G1具備如下特點(diǎn):

  • 并行與并發(fā) G1 能充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢废登,使用多個CPU來縮短“Stop The World”停頓時間怠缸,部分其他收集器原本需要停頓Java線程執(zhí)行的GC動作,G1收集器仍然可以通過并發(fā)的方式讓Java程序繼續(xù)執(zhí)行钳宪。
  • 分代收集 與其他收集器一樣揭北,分代概念在G1中依然得以保留。雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個GC堆吏颖,但它能夠采用不同方式去處理新創(chuàng)建的對象和已存活一段時間搔体、熬過多次GC的舊對象來獲取更好的收集效果。
  • 空間整合 G1從整體來看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器半醉,從局部(兩個Region之間)上來看是基于“復(fù)制”算法實(shí)現(xiàn)的疚俱。這意味著G1運(yùn)行期間不會產(chǎn)生內(nèi)存空間碎片,收集后能提供規(guī)整的可用內(nèi)存缩多。此特性有利于程序長時間運(yùn)行呆奕,分配大對象時不會因?yàn)闊o法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC养晋。
  • 可預(yù)測的停頓 這是G1相對CMS的一大優(yōu)勢,降低停頓時間是G1和CMS共同的關(guān)注點(diǎn)梁钾,但G1除了降低停頓外绳泉,還能建立可預(yù)測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi)姆泻,消耗在GC上的時間不得超過N毫秒零酪,這幾乎已經(jīng)是實(shí)時Java(RTSJ)的垃圾收集器的特征了。

橫跨整個堆內(nèi)存

在G1之前的其他收集器進(jìn)行收集的范圍都是整個新生代或者老生代拇勃,而G1不再是這樣四苇。G1在使用時,Java堆的內(nèi)存布局與其他收集器有很大區(qū)別方咆,它將整個Java堆劃分為多個大小相等的獨(dú)立區(qū)域(Region)月腋,雖然還保留新生代和老年代的概念,但新生代和老年代不再是物理隔離的了瓣赂,而都是一部分Region(不需要連續(xù))的集合罗售。

建立可預(yù)測的時間模型

G1收集器之所以能建立可預(yù)測的停頓時間模型,是因?yàn)樗梢?strong>有計(jì)劃地避免在整個Java堆中進(jìn)行全區(qū)域的垃圾收集钩述。G1跟蹤各個Region里面的垃圾堆積的價值大姓辍(回收所獲得的空間大小以及回收所需時間的經(jīng)驗(yàn)值),在后臺維護(hù)一個優(yōu)先列表牙勘,每次根據(jù)允許的收集時間职恳,優(yōu)先回收價值最大的Region(這也就是Garbage-First名稱的來由)。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級的區(qū)域回收方式方面,保證了G1收集器在有限的時間內(nèi)可以獲取盡可能高的收集效率放钦。

避免全堆掃描——Remembered Set

G1把Java堆分為多個Region,就是“化整為零”恭金。但是Region不可能是孤立的操禀,一個對象分配在某個Region中,可以與整個Java堆任意的對象發(fā)生引用關(guān)系横腿。在做可達(dá)性分析確定對象是否存活的時候颓屑,需要掃描整個Java堆才能保證準(zhǔn)確性,這顯然是對GC效率的極大傷害耿焊。

為了避免全堆掃描的發(fā)生揪惦,虛擬機(jī)為G1中每個Region維護(hù)了一個與之對應(yīng)的Remembered Set。虛擬機(jī)發(fā)現(xiàn)程序在對Reference類型的數(shù)據(jù)進(jìn)行寫操作時罗侯,會產(chǎn)生一個Write Barrier暫時中斷寫操作器腋,檢查Reference引用的對象是否處于不同的Region之中(在分代的例子中就是檢查是否老年代中的對象引用了新生代中的對象),如果是,便通過CardTable把相關(guān)引用信息記錄到被引用對象所屬的Region的Remembered Set之中纫塌。當(dāng)進(jìn)行內(nèi)存回收時诊县,在GC根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏。


如果不計(jì)算維護(hù)Remembered Set的操作措左,G1收集器的運(yùn)作大致可劃分為以下幾個步驟:

  • 初始標(biāo)記(Initial Marking) 僅僅只是標(biāo)記一下GC Roots 能直接關(guān)聯(lián)到的對象依痊,并且修改TAMS(Nest Top Mark Start)的值,讓下一階段用戶程序并發(fā)運(yùn)行時媳荒,能在正確可以的Region中創(chuàng)建對象抗悍,此階段需要停頓線程驹饺,但耗時很短钳枕。
  • 并發(fā)標(biāo)記(Concurrent Marking) 從GC Root 開始對堆中對象進(jìn)行可達(dá)性分析,找到存活對象赏壹,此階段耗時較長鱼炒,但可與用戶程序并發(fā)執(zhí)行
  • 最終標(biāo)記(Final Marking) 為了修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分標(biāo)記記錄蝌借,虛擬機(jī)將這段時間對象變化記錄在線程的Remembered Set Logs里面昔瞧,最終標(biāo)記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中,這階段需要停頓線程菩佑,但是可并行執(zhí)行自晰。
  • 篩選回收(Live Data Counting and Evacuation) 首先對各個Region中的回收價值和成本進(jìn)行排序,根據(jù)用戶所期望的GC 停頓是時間來制定回收計(jì)劃稍坯。此階段其實(shí)也可以做到與用戶程序一起并發(fā)執(zhí)行酬荞,但是因?yàn)橹换厥找徊糠諶egion,時間是用戶可控制的瞧哟,而且停頓用戶線程將大幅度提高收集效率混巧。

通過下圖可以比較清楚地看到G1收集器的運(yùn)作步驟中并發(fā)和需要停頓的階段(Safepoint處):

image

4.3.5 總結(jié)

收集器 串行、并行or并發(fā) 新生代/老年代 算法 目標(biāo) 適用場景
Serial 串行 新生代 復(fù)制算法 單CPU環(huán)境下的Client模式勤揩。適合對停頓要求不高的應(yīng)用
Serial Old 串行 老年代 標(biāo)記-整理 單CPU環(huán)境下的Client模式咧党、CMS的后備預(yù)案
ParNew 并行 新生代 復(fù)制算法 吞吐量優(yōu)先 多CPU環(huán)境時在Server模式下與CMS配合
Parallel Scavenge 并行 新生代 復(fù)制算法 吞吐量優(yōu)先 在后臺運(yùn)算而不需要太多交互的任務(wù)
Parallel Old 并行 老年代 標(biāo)記-整理 吞吐量優(yōu)先 在后臺運(yùn)算而不需要太多交互的任務(wù)
CMS 并發(fā) 老年代 標(biāo)記-清除 響應(yīng)速度優(yōu)先 集中在互聯(lián)網(wǎng)站或B/S系統(tǒng)服務(wù)端上的Java應(yīng)用
G1 并發(fā) both 標(biāo)記-整理+復(fù)制算法 響應(yīng)速度優(yōu)先 面向服務(wù)端應(yīng)用,將來替換CMS

垃圾收集器參數(shù)總結(jié):

參數(shù) 描述
UseSerialGC 虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)值陨亡,打開此開關(guān)后傍衡,使用Serial+Serial Old的收集器組合進(jìn)行內(nèi)存回收
UseParNewGC 打開此開關(guān)后,使用ParNew+Serial Old的收集器組合進(jìn)行內(nèi)存回收
UseConcMarkSweepGC 打開此開關(guān)后负蠕,使用ParNew+CMS+Serial Old的收集器組合進(jìn)行內(nèi)存回收聪舒。Serial Old收集器將作為CMS收集器出現(xiàn)Concurrent Mode Failure失敗后的后備收集器使用
UseParallelGC 虛擬機(jī)運(yùn)行在Server模式下的默認(rèn)值,打開此開關(guān)后虐急,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器組合進(jìn)行內(nèi)存回收
UseParallelOldGC 打開此開關(guān)后箱残,使用Parallel Scavenge + Parallel Old的收集器組合進(jìn)行內(nèi)存回收
SurvivorRatio 新生代中Eden區(qū)域與Survivor區(qū)域的容量比值,默認(rèn)值為8,代表Eden:Survivor=8:1
PretenureSizeThreshold 直接晉升到老年代的對象大小被辑,設(shè)置這個參數(shù)后燎悍,大于這個參數(shù)的對象將直接在老年代分配
MaxTenuringThreshold 晉升到老年代的對象年齡,每個對象在堅(jiān)持過一次Minor GC之后盼理,年齡就增加1谈山,當(dāng)超過這個參數(shù)時就進(jìn)入老年代
UseAdaptiveSizePolicy 動態(tài)調(diào)整Java堆中各個區(qū)域的大小以及進(jìn)入老年代的年齡
HandlePromotionFailure 是否允許分配擔(dān)保失敗,即老年代的剩余空間不足以應(yīng)付新生代的整個Eden和Survivor區(qū)的所有對象都存活的極端情況
ParallelGCThreads 設(shè)置并行GC時進(jìn)行內(nèi)存回收的線程數(shù)
GCTimeRatio GC時間占總時間的比率宏怔,默認(rèn)值為99奏路,即允許1%的GC時間。僅在使用Parallel Scavenge收集器時生效
MaxGCPauseMillis 設(shè)置GC的最大停頓時間臊诊,僅在使用Parallel Scavenge收集器時生效
CMSInitingOccupancyFraction 設(shè)置CMS收集器在老年代空間被使用多少后觸發(fā)垃圾收集鸽粉。默認(rèn)值為68%,僅在使用CMS收集器時生效
UseCMSCompactAtFullCollection 設(shè)置CMS收集器在完成垃圾收集后是否要進(jìn)行一次內(nèi)存碎片整理抓艳,僅在使用CMS收集器時生效
CMSFullGCsBeforeCompaction 設(shè)置CMS收集器在進(jìn)行若干次垃圾收集后再啟動一次內(nèi)存碎片整理触机。僅在使用CMS收集器時生效

4.5 應(yīng)用程序?qū)占鞯挠绊?/h2>
  1. 內(nèi)存分配
  2. 存活數(shù)據(jù)的多少
  3. 老年代中的引用更新。

不好的編程實(shí)踐:

  1. 對象池化
  2. 不合適的數(shù)組類數(shù)據(jù)結(jié)構(gòu)尺寸

5. 參考

7種垃圾收集器
如何優(yōu)化Java GC「譯」

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玷或,一起剝皮案震驚了整個濱河市儡首,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌偏友,老刑警劉巖蔬胯,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異位他,居然都是意外死亡氛濒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門棱诱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泼橘,“玉大人,你說我怎么就攤上這事迈勋【婷穑” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵靡菇,是天一觀的道長重归。 經(jīng)常有香客問我,道長厦凤,這世上最難降的妖魔是什么鼻吮? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮较鼓,結(jié)果婚禮上椎木,老公的妹妹穿的比我還像新娘违柏。我一直安慰自己,他們只是感情好香椎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布漱竖。 她就那樣靜靜地躺著,像睡著了一般畜伐。 火紅的嫁衣襯著肌膚如雪馍惹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天玛界,我揣著相機(jī)與錄音万矾,去河邊找鬼。 笑死慎框,一個胖子當(dāng)著我的面吹牛良狈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鲤脏,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼们颜,長吁一口氣:“原來是場噩夢啊……” “哼吕朵!你這毒婦竟也來了猎醇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤努溃,失蹤者是張志新(化名)和其女友劉穎硫嘶,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梧税,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沦疾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了第队。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哮塞。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凳谦,靈堂內(nèi)的尸體忽然破棺而出忆畅,到底是詐尸還是另有隱情,我是刑警寧澤尸执,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布家凯,位于F島的核電站,受9級特大地震影響如失,放射性物質(zhì)發(fā)生泄漏绊诲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一褪贵、第九天 我趴在偏房一處隱蔽的房頂上張望掂之。 院中可真熱鬧,春花似錦、人聲如沸世舰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冯乘。三九已至洽胶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裆馒,已是汗流浹背姊氓。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喷好,地道東北人翔横。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像梗搅,于是被迫代替她去往敵國和親禾唁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容