來(lái)自: Android夢(mèng)想特工隊(duì)
作者: Aaron
主頁(yè): http://www.wxtlife.com/
原文連接: http://www.wxtlife.com/2016/04/25/java-jvm-gc/
如果想加入Android技術(shù)交流群,請(qǐng)長(zhǎng)按識(shí)別二維碼關(guān)注下方公眾號(hào),點(diǎn)擊“加群”獲取加群方式。
在java中垃圾回收是系統(tǒng)自動(dòng)完成的,了解它對(duì)優(yōu)化應(yīng)用程序有很大的幫助。那么我們就從下面幾個(gè)方面來(lái)了解垃圾回收機(jī)制:
- 哪些對(duì)象需要回收?
- 什么時(shí)候回收?
- 怎么去回收?
判斷對(duì)象可以回收的方法:
-
引用計(jì)數(shù)算法
引用計(jì)數(shù)
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。
優(yōu)點(diǎn):簡(jiǎn)單,高效,現(xiàn)在的objective-c用的就是這種算法。
缺點(diǎn):很難處理循環(huán)引用,相互引用的兩個(gè)對(duì)象則無(wú)法釋放。
-
可達(dá)性分析算法(根搜索算法)
可達(dá)性分析
從GC Roots(每種具體實(shí)現(xiàn)對(duì)GC Roots有不同的定義)作為起點(diǎn),向下搜索它們引用的對(duì)象,可以生成一棵引用樹(shù),樹(shù)的節(jié)點(diǎn)視為可達(dá)對(duì)象,反之視為不可達(dá)。
在Java語(yǔ)言中,可以作為GC Roots的對(duì)象包括下面幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中的引用對(duì)象。
- 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象。
- 方法區(qū)中的常量引用的對(duì)象。
- 本地方法棧中JNI(Native方法)的引用對(duì)象
真正標(biāo)記以為對(duì)象為可回收狀態(tài)至少要標(biāo)記兩次。
常見(jiàn)概念介紹
引用類型分類
引用類型有四種類型分別是:強(qiáng)引用(Strong Reference)、軟引用(soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference),其中強(qiáng)引用是我們常用到的。下面來(lái)說(shuō)明下他們什么時(shí)候會(huì)被垃圾回收機(jī)制回收。
強(qiáng)引用: 這種最頑強(qiáng),只要有一個(gè)引用存在,永遠(yuǎn)都不會(huì)被回收掉。
軟引用: 一般是指還有用,但是非必須的對(duì)象。在內(nèi)存空間不足的情況下,會(huì)回收掉此部分內(nèi)存,如果還不夠則會(huì)拋出內(nèi)存溢出異常。
弱引用: 一般指非必須的對(duì)象,比軟引用還要弱,它只能生存到下一次垃圾回收前,如果一旦發(fā)生垃圾回收,它將會(huì)被回收掉。
虛引用: 最弱的引用關(guān)系,無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象的實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用的唯一目的就是能夠在這個(gè)對(duì)象被回收的時(shí)候收到一個(gè)系統(tǒng)通知。
并發(fā)與并行
并行(Parallel): 指多條垃圾收集線程并行工作,但此時(shí)用戶線程仍然處于等待狀態(tài)。
并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的,可能會(huì)交替執(zhí)行),用戶程序在繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行于另一個(gè)CPU上。
Minor GC 和 Full GC
新生代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倍以上。
吞吐量
吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量 = 運(yùn)行用戶代碼時(shí)間 /(運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)。
虛擬機(jī)總共運(yùn)行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。
Stop the world 概念
因?yàn)槔厥盏臅r(shí)候,需要整個(gè)的引用狀態(tài)保持不變,否則判定是判定垃圾,等我稍后回收的時(shí)候它又被引用了,這就全亂套了。所以,GC的時(shí)候,其他所有的程序執(zhí)行處于暫停狀態(tài),卡住了。
幸運(yùn)的是,這個(gè)卡頓是非常短(尤其是新生代),對(duì)程序的影響微乎其微 (關(guān)于其他GC比如并發(fā)GC之類的,在此不討論)。
所以GC的卡頓問(wèn)題由此而來(lái),也是情有可原,暫時(shí)無(wú)可避免。
finalize需要注意地方
同一個(gè)對(duì)象的finalize 方法只會(huì)執(zhí)行一次。
在給對(duì)象A賦值為空后進(jìn)行手動(dòng)調(diào)用gc回收內(nèi)存,會(huì)執(zhí)行復(fù)寫(xiě)了finalize方法,在finalize中再將A賦值一個(gè)非空的值,再次進(jìn)行賦值為空,調(diào)用gc,之后就不會(huì)再執(zhí)行finalize方法了。
幾種垃圾回收算法
- 標(biāo)記清除算法
- 復(fù)制算法算法
- 標(biāo)記整理算法
- 分帶回收算法
標(biāo)記清除算法
這個(gè)按名字就很容易理解,就是標(biāo)記哪些是可回收的,然后進(jìn)行清除回收處理。標(biāo)記-清除算法分為兩個(gè)階段:標(biāo)記階段和清除階段,標(biāo)記階段主要為標(biāo)記那些對(duì)象是可以回收的,清除就是回收標(biāo)記過(guò)的那部分內(nèi)存空間。
優(yōu)點(diǎn): 簡(jiǎn)單,易實(shí)現(xiàn)
缺點(diǎn): 容易產(chǎn)生內(nèi)存碎片,對(duì)于后面分配大空間時(shí),找不到足夠的空間,而主動(dòng)會(huì)觸發(fā)一次內(nèi)存回收,增加內(nèi)存回收的次數(shù)。
復(fù)制算法
此方法將內(nèi)存按容量分為兩塊,例如A、B兩塊,每次只使用其中的一塊,當(dāng)要進(jìn)行回收操作時(shí),將A中還存活的對(duì)象復(fù)制到B塊中(假設(shè)上次使用A),然后對(duì)A中所有對(duì)象清空就又構(gòu)成一個(gè)完整的內(nèi)存塊。這種方法就避免了標(biāo)記清除的內(nèi)存碎片問(wèn)題。
優(yōu)點(diǎn): 快速高效,不會(huì)產(chǎn)生內(nèi)存碎片。
缺點(diǎn): 可用內(nèi)存會(huì)減少一半,因?yàn)槭前凑站值摹?/p>
注意: 效率也與存活對(duì)象的多少有關(guān),如果存活對(duì)象多,復(fù)制就多,效率就低了。
標(biāo)記整理法
標(biāo)記整理法就是在標(biāo)記清除方法上進(jìn)行的優(yōu)化,主要是在標(biāo)記完成后將這些存活的對(duì)象向一端移動(dòng),然后將末尾邊界后的所有內(nèi)存空間清除。
優(yōu)點(diǎn): 適合存活對(duì)象多的,不產(chǎn)生內(nèi)存碎片
分代回收法
分代回收算法實(shí)際上是把復(fù)制算法和標(biāo)記整理法的結(jié)合,并不是真正一個(gè)新的算法,一般分為:老年代(Old Generation)和新生代(Young Generation),老年代就是很少垃圾需要進(jìn)行回收的,新生代就是有很多的內(nèi)存空間需要回收,所以不同代就采用不同的回收算法,以此來(lái)達(dá)到高效的回收算法。
新生代:由于新生代產(chǎn)生很多臨時(shí)對(duì)象,大量對(duì)象需要進(jìn)行回收,所以采用復(fù)制算法是最高效的。
老年代:回收的對(duì)象很少,都是經(jīng)過(guò)幾次標(biāo)記后都不是可回收的狀態(tài)轉(zhuǎn)移到老年代的,所以僅有少量對(duì)象需要回收,故采用標(biāo)記清除或者標(biāo)記整理算法。
所以以上整個(gè)過(guò)程就達(dá)到了最高效的回收辦法。
注意:新生代與老年代分配的比例為 2:1
分代回收法詳解
分代算法主要核心思想是上面兩種算法的結(jié)合,但是它里面還有一些特殊的地方,所以這里進(jìn)行特別的說(shuō)明。
上面用到的復(fù)制算法,按照前面講的,按照空間1:1分配的,但是分代算法中不是這樣的,而是按照8:2 (4:1)的比例進(jìn)行分配的,其中后面一塊又等分成了兩塊,也就是8:1:1的比例,8為新生代(Eden區(qū)),1分別為兩塊等大小的Survivor區(qū)。
所有新創(chuàng)建的對(duì)象都是放在Eden區(qū)的,所以有很多的臨時(shí)變量,故每次大部分回收的垃圾都是在Eden區(qū)的,所以Eden區(qū)會(huì)分配那么多的空間,那為什么要分兩塊Survivor區(qū)呢? 是因?yàn)椋谶M(jìn)行一次coping算法回收時(shí)將Eden區(qū)中存活的對(duì)象復(fù)制到Survivor區(qū),然后在進(jìn)行一次回收時(shí),Survivor區(qū)的存活對(duì)象沒(méi)地方存放了,因?yàn)镋den區(qū)每次都有新創(chuàng)建的對(duì)象存在。故在新建一塊Survivor B區(qū),這時(shí)將Eden和Survivor A區(qū)存活的對(duì)象放在B區(qū),然后清空Eden和Survivor A區(qū)。這樣就相當(dāng)于A和B每次回收后都有一個(gè)是全新的也就是空的,就是為了循環(huán)這種操作。
分代回收,內(nèi)存是如何分配的
- 新創(chuàng)建一個(gè)對(duì)象,默認(rèn)是分在Eden區(qū)的,當(dāng)Eden區(qū)內(nèi)存不夠時(shí),觸發(fā)一次minor gc (新生代回收),Eden區(qū)存活對(duì)象放入Survivor A區(qū),然后新對(duì)象放入Eden區(qū)。
- 再新建一個(gè)對(duì)象,放入Eden區(qū)發(fā)現(xiàn)又滿了,在進(jìn)行minor gc,這時(shí)將Eden區(qū)和Survivor A區(qū)存活的對(duì)象移動(dòng)到Survivor B區(qū),然后清空Eden和Survivor A。
- 如果再來(lái)一個(gè)新對(duì)象,Eden區(qū)又滿了,則再進(jìn)行minor gc,移動(dòng)到A區(qū),然后清空Eden和B區(qū),如果多次這樣,有些長(zhǎng)久的對(duì)象就會(huì)在不斷的Survivor A和Survivor B區(qū)之間來(lái)回移動(dòng),多次之后,虛擬機(jī)默認(rèn)15次后,就會(huì)將這些對(duì)象全部移動(dòng)到老年區(qū)區(qū)。
- 如果新對(duì)象在eden區(qū)放不下,則直接放入老年代
- 如果Survivor 區(qū)放不下的對(duì)象,也直接放入老年代
- 發(fā)現(xiàn)老年代也滿了,則進(jìn)行一次Full gc (major gc)
火車(chē)回收算法
垃圾收集算法一個(gè)很大的缺點(diǎn)就是難以控制垃圾回收所占用的CPU時(shí)間,以及何時(shí)需要進(jìn)行垃圾回收。火車(chē)算法是分代收集器所用的算法,目的是在成熟對(duì)象空間中提供限定時(shí)間的漸進(jìn)收集。目前應(yīng)用于SUN公司的Hotspot虛擬機(jī)上。
在火車(chē)算法中,內(nèi)存被分為塊,多個(gè)塊組成一個(gè)集合。為了形象化,一節(jié)車(chē)廂代表一個(gè)塊,一列火車(chē)代表一個(gè)集合。
注意每個(gè)車(chē)廂大小相等,但每個(gè)火車(chē)包含的車(chē)廂數(shù)不一定相等。垃圾收集以車(chē)廂為單位,收集順序按被創(chuàng)建的先后順序進(jìn)行。
具體參考:http://blog.csdn.net/feier7501/article/details/21748615
常用垃圾回收機(jī)制
- 標(biāo)記-清除收集器
這種收集器首先遍歷對(duì)象圖并標(biāo)記可到達(dá)的對(duì)象,然后掃描堆棧以尋找未標(biāo)記對(duì)象并釋放它們的內(nèi)存。這種收集器一般使用單線程工作并停止其他操作。 - 標(biāo)記-壓縮收集器
有時(shí)也叫標(biāo)記-清除-壓縮收集器,與標(biāo)記-清除收集器有相同的標(biāo)記階段。在第二階段,則把標(biāo)記對(duì)象復(fù)制到堆棧的新域中以便壓縮堆棧。這種收集器也停止其他操作。 - 復(fù)制收集器
這種收集器將堆棧分為兩個(gè)域,常稱為半空間。每次僅使用一半的空間,虛擬機(jī)生成的新對(duì)象則放在另一半空間中。垃圾回收器運(yùn)行時(shí),它把可到達(dá)對(duì)象復(fù)制到另一半空間,沒(méi)有被復(fù)制的的對(duì)象都是不可達(dá)對(duì)象,可以被回收。這種方法適用于短生存期的對(duì)象,持續(xù)復(fù)制長(zhǎng)生存期的對(duì)象由于多次拷貝,導(dǎo)致效率降低。缺點(diǎn)是只有一半的虛擬機(jī)空間得到使用。 - 增量收集器
增量收集器把堆棧分為多個(gè)域,每次僅從一個(gè)域收集垃圾。這會(huì)造成較小的應(yīng)用程序中斷。 - 分代收集器
這種收集器把堆棧分為兩個(gè)或多個(gè)域,用以存放不同壽命的對(duì)象。虛擬機(jī)生成的新對(duì)象一般放在其中的某個(gè)域中。過(guò)一段時(shí)間,繼續(xù)存在的對(duì)象將獲得使用期并轉(zhuǎn)入更長(zhǎng)壽命的域中。分代收集器對(duì)不同的域使用不同的算法以優(yōu)化性能。這樣可以減少?gòu)?fù)制對(duì)象的時(shí)間。 - 并發(fā)收集器
并發(fā)收集器與應(yīng)用程序同時(shí)運(yùn)行。這些收集器在某點(diǎn)上(比如壓縮時(shí))一般都不得不停止其他操作以完成特定的任務(wù),但是因?yàn)槠渌麘?yīng)用程序可進(jìn)行其他的后臺(tái)操作,所以中斷其他處理的實(shí)際時(shí)間大大降低。 - 并行收集器
并行收集器使用某種傳統(tǒng)的算法并使用多線程并行的執(zhí)行它們的工作。在多CPU機(jī)器上使用多線程技術(shù)可以顯著的提高java應(yīng)用程序的可擴(kuò)展性。 - 自適應(yīng)收集器
根據(jù)程序運(yùn)行狀況以及堆的使用狀況,自動(dòng)選一種合適的垃圾回收算法。這樣可以不局限與一種垃圾回收算法。
幾種垃圾收集器
常見(jiàn)的垃圾收集器有:serial收集器、Parallel收集器、Parallel Old 垃圾收集器、CMS(Concurrent Mark-Sweep)收集器、G1收集器.其中Serial收集器為串行收集器,其他均為并行收集器。
串行垃圾回收器(Serial Garbage Collector)
并行垃圾回收器(Parallel Garbage Collector)
并發(fā)標(biāo)記掃描垃圾回收器(CMS Garbage Collector)
G1垃圾回收器(G1 Garbage Collector)
Serial收集器->串行收集器 (-XX:+UseSerialGC)
最古老,最穩(wěn)定,簡(jiǎn)單而高效,可能會(huì)產(chǎn)生較長(zhǎng)的停頓。
Serial是一個(gè)單線程
的收集器,它不僅僅只會(huì)使用一個(gè)CPU或一條線程去完成垃圾收集工作,并且在進(jìn)行垃圾收集的同時(shí),必須暫停其他所有的工作線程,直到垃圾收集結(jié)束。
Serial垃圾收集器雖然在收集垃圾過(guò)程中需要暫停所有其他的工作線程,但是它簡(jiǎn)單高效,對(duì)于限定單個(gè)CPU環(huán)境來(lái)說(shuō),沒(méi)有線程交互的開(kāi)銷,可以獲得最高的單線程垃圾收集效率,因此Serial垃圾收集器依然是java虛擬機(jī)運(yùn)行在Client模式下默認(rèn)的新生代垃圾收集器。
Serial Old收集器
Serial Old是Serial垃圾收集的老年代版本。它同樣是個(gè)單線程的收集器,使用標(biāo)記-整理算法,這個(gè)收集器也主要是運(yùn)行在Client默認(rèn)的java虛擬機(jī)默認(rèn)的年老代垃圾收集器。
ParNew收集器 (-XX:+UseParallelGC)
ParNew收集器其實(shí)就是Serial收集器的多線程
版本,除了使用多條線程進(jìn)行垃圾收集之外,其余行為包括Serial收集器可用的所有控制參數(shù)、收集算法、Stop The World、對(duì)象分配規(guī)則、回收策略等都與Serial收集器完全一樣,在實(shí)現(xiàn)上,這兩種收集器也共用了相當(dāng)多的代碼。ParNew收集器是許多運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器。
Parallel Scavenge
Parallel Scavenge是一個(gè)新生代收集器,使用多線程和復(fù)制算法。相比其他收集器,只有這個(gè)收集器是針對(duì)系統(tǒng)吞吐量進(jìn)行改進(jìn),適用于后臺(tái)運(yùn)算并且交互不多的程序。其他收集器則更關(guān)注改善收集時(shí)的停頓時(shí)間,適用于用戶交互的程序。
Parallel Old 垃圾收集器(-XX:+UseParallelOldGC)
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法。
在注重吞吐量以及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收集器是基于“標(biāo)記—清除”算法實(shí)現(xiàn)的。整個(gè)過(guò)程需要下面四個(gè)步驟。
- 初始標(biāo)記(CMS initial mark)
初始標(biāo)記僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快,需要“Stop The World”。 - 并發(fā)標(biāo)記(CMS concurrent mark)
并發(fā)標(biāo)記階段就是進(jìn)行GC Roots Tracing的過(guò)程。 - 重新標(biāo)記(CMS remark)
重新標(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í)間短,仍然需要“Stop The World”。 - 并發(fā)清除(CMS concurrent sweep)
并發(fā)清除階段會(huì)清除對(duì)象。
** 優(yōu)點(diǎn):** 并發(fā)收集、低停頓。
缺點(diǎn):
CMS收集器對(duì)CPU資源非常敏感,以為在并發(fā)階段占用一部分線程(CPU資源),導(dǎo)致應(yīng)用程序變慢,總吞吐量變低。CMS默認(rèn)啟動(dòng)的回收線程數(shù)是(CPU數(shù)量+3)/4
,也就是當(dāng)CPU在4個(gè)以上時(shí),并發(fā)回收時(shí)垃圾收集線程不少于25%的CPU資源,并且隨著CPU數(shù)量的增加而下降。
CMS收集器無(wú)法處理浮動(dòng)垃圾,可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生。
也是由于在垃圾收集階段用戶線程還需要運(yùn)行,那也就還需要預(yù)留有足夠的內(nèi)存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進(jìn)行收集,需要預(yù)留一部分空間提供并發(fā)收集時(shí)的程序運(yùn)作使用。要是CMS運(yùn)行期間預(yù)留的內(nèi)存無(wú)法滿足程序需要,就會(huì)出現(xiàn)一次“Concurrent Mode Failure”失敗,這時(shí)虛擬機(jī)將啟動(dòng)后備預(yù)案:臨時(shí)啟用Serial Old收集器來(lái)重新進(jìn)行老年代的垃圾收集,這樣停頓時(shí)間就很長(zhǎng)了。
CMS是一款基于“標(biāo)記—清除”算法實(shí)現(xiàn)的收集器,這意味著收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生。空間碎片過(guò)多時(shí),將會(huì)給大對(duì)象分配帶來(lái)很大麻煩,往往會(huì)出現(xiàn)老年代還有很大空間剩余,但是無(wú)法找到足夠大的連續(xù)空間來(lái)分配當(dāng)前對(duì)象,不得不提前觸發(fā)一次Full GC。
浮動(dòng)垃圾: 由于CMS并發(fā)清理階段用戶線程還在運(yùn)行著,伴隨程序運(yùn)行自然就還會(huì)有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過(guò)程之后,CMS無(wú)法在當(dāng)次收集中處理掉它們,只好留待下一次GC時(shí)再清理掉,本地?zé)o法清理的垃圾則稱為浮動(dòng)垃圾。
G1 收集器
G1收集器是當(dāng)前收集器技術(shù)發(fā)展最前沿的成果,一款面向服務(wù)端應(yīng)用的垃圾收集器。基于標(biāo)記-整理算法,也就是說(shuō)不會(huì)產(chǎn)生內(nèi)存碎片,可以精確控制停頓。基本不犧牲吞吐量的前提下完成低停頓的內(nèi)存回收。這是由于它將新生代、老年代劃分為多個(gè)區(qū)域,并維護(hù)一個(gè)每個(gè)區(qū)域收集的優(yōu)先列表,保證了在有限的時(shí)間內(nèi)可以獲得最高的收集效率。收集的范圍是整個(gè)JAVA堆。而不是在區(qū)分新生代,老年代。
執(zhí)行過(guò)程:
- 初始標(biāo)記(Initial Marking)
初始標(biāo)記階段僅僅只是標(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)
并發(fā)標(biāo)記階段是從GC Root開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,找出存活的對(duì)象,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行。 - 最終標(biāo)記(Final Marking)
最終標(biāo)記階段是為了修正在并發(fā)標(biāo)記期間因用戶程序繼續(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 and Evacuation)
篩選回收階段首先對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時(shí)間來(lái)制定回收計(jì)劃,這個(gè)階段其實(shí)也可以做到與用戶程序一起并發(fā)執(zhí)行,但是因?yàn)橹换厥找徊糠諶egion,時(shí)間是用戶可控制的,而且停頓用戶線程將大幅提高收集效率。
特點(diǎn):
- 并行與并發(fā)
可使用多個(gè)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毫秒。
在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)可以獲取盡可能高的收集效率
幾種收集器對(duì)比
Serial收集器 VS ParNew收集器
ParNew收集器在單CPU的環(huán)境中絕對(duì)不會(huì)有比Serial收集器更好的效果,甚至由于存在線程交互的開(kāi)銷,該收集器在通過(guò)超線程技術(shù)實(shí)現(xiàn)的兩個(gè)CPU的環(huán)境中都不能百分之百地保證可以超越Serial收集器。
然而,隨著可以使用的CPU的數(shù)量的增加,它對(duì)于GC時(shí)系統(tǒng)資源的有效利用還是很有好處的。
Parallel Scavenge收集器 VS CMS等收集器:
Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量(Throughput)。
由于與吞吐量關(guān)系密切,Parallel Scavenge收集器也經(jīng)常稱為“吞吐量?jī)?yōu)先”收集器。
Parallel Scavenge收集器 VS ParNew收集器:
Parallel Scavenge收集器與ParNew收集器的一個(gè)重要區(qū)別是它具有自適應(yīng)調(diào)節(jié)策略。
GC自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics):
Parallel Scavenge收集器有一個(gè)參數(shù)-XX:+UseAdaptiveSizePolicy。當(dāng)這個(gè)參數(shù)打開(kāi)之后,就不需要手工指定新生代的大小、Eden與Survivor區(qū)的比例、晉升老年代對(duì)象年齡等細(xì)節(jié)參數(shù)了,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大的吞吐量,這種調(diào)節(jié)方式稱為GC自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics)。
觸發(fā)GC的類型
了解這些是為了解決實(shí)際問(wèn)題,Java虛擬機(jī)會(huì)把每次觸發(fā)GC的信息打印出來(lái)來(lái)幫助我們分析問(wèn)題,所以掌握觸發(fā)GC的類型是分析日志的基礎(chǔ)。
GC_FOR_MALLOC 表示是在堆上分配對(duì)象時(shí)內(nèi)存不足觸發(fā)的GC。
GC_CONCURRENT 當(dāng)我們應(yīng)用程序的堆內(nèi)存達(dá)到一定量,或者可以理解為快要滿的時(shí)候,系統(tǒng)會(huì)自動(dòng)觸發(fā)GC操作來(lái)釋放內(nèi)存。
GC_EXPLICIT 表示是應(yīng)用程序調(diào)用System.gc、VMRuntime.gc接口或者收到SIGUSR1信號(hào)時(shí)觸發(fā)的GC。
GC_BEFORE_OOM 表示是在準(zhǔn)備拋OOM異常之前進(jìn)行的最后努力而觸發(fā)的GC。
垃圾回收的JVM配置
運(yùn)行的垃圾回收器類型
配置 | 描述 |
---|---|
-XX:+UseSerialGC | 串行垃圾回收器 |
-XX:+UseParallelGC | 并行垃圾回收器 |
-XX:+UseConcMarkSweepGC | 并發(fā)標(biāo)記掃描垃圾回收器 |
-XX:ParallelCMSThreads= | 并發(fā)標(biāo)記掃描垃圾回收器,=為使用的線程數(shù)量 |
-XX:+UseG1GC | G1垃圾回收器 |
GC的優(yōu)化配置
配置 | 描述 |
---|---|
-Xms | 初始化堆內(nèi)存大小 |
-Xmx | 堆內(nèi)存最大值 |
-Xmn | 新生代大小 |
-XX:PermSize | 初始化永久代大小 |
-XX:MaxPermSize | 永久代最大容量 |
使用JVM GC參數(shù)的例子
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar
總結(jié)
內(nèi)存回收與垃圾收集器在很多時(shí)候都是影響系統(tǒng)性能,并發(fā)能力的主要因素之一,虛擬機(jī)之所以提供多種不同的收集器及大量的參數(shù)調(diào)節(jié),是因?yàn)橹挥懈鶕?jù)具體實(shí)際需要,實(shí)現(xiàn)方式選擇最優(yōu)的收集方式才能保證最優(yōu)的性能。沒(méi)有固定收集器、參數(shù)組合,也沒(méi)有最優(yōu)的調(diào)優(yōu)方法,虛擬機(jī)也沒(méi)有什么必然的內(nèi)存回收行為。因?yàn)橹钡浆F(xiàn)在為止還沒(méi)有最好的收集器出現(xiàn),更加沒(méi)有萬(wàn)能的收集器。
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
收集器適用場(chǎng)景:
用戶交互:ParNew、CMS
高吞吐量:Parallel Scavenge
上面有7中收集器,分為兩塊,上面為新生代收集器,下面是老年代收集器。如果兩個(gè)收集器之間存在連線,就說(shuō)明它們可以搭配使用。
用文字描述收集器可配合使用結(jié)果如下:
Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Parallel Old、Parallel Scavenge/Serial Old、CMS/Serial Old
附錄:
垃圾收集器參數(shù)總結(jié)
-XX:+<option> 啟用選項(xiàng)
-XX:-<option> 不啟用選項(xiàng)
-XX:<option>=<number>
-XX:<option>=<string>
參數(shù) | 描述 |
---|---|
-XX:+UseSerialGC | Jvm運(yùn)行在Client模式下的默認(rèn)值,打開(kāi)此開(kāi)關(guān)后,使用Serial + Serial Old的收集器組合進(jìn)行內(nèi)存回收 |
-XX:+UseParNewGC | 打開(kāi)此開(kāi)關(guān)后,使用ParNew + Serial Old的收集器進(jìn)行垃圾回收 |
-XX:+UseConcMarkSweepGC | 使用ParNew + CMS + Serial Old的收集器組合進(jìn)行內(nèi)存回收,Serial Old作為CMS出現(xiàn)“Concurrent Mode Failure”失敗后的后備收集器使用。 |
-XX:+UseParallelGC | Jvm運(yùn)行在Server模式下的默認(rèn)值,打開(kāi)此開(kāi)關(guān)后,使用Parallel Scavenge + Serial Old的收集器組合進(jìn)行回收 |
-XX:+UseParallelOldGC | 使用Parallel Scavenge + Parallel Old的收集器組合進(jìn)行回收 |
-XX:SurvivorRatio | 新生代中Eden區(qū)域與Survivor區(qū)域的容量比值,默認(rèn)為8,代表Eden:Subrvivor = 8:1 |
-XX:PretenureSizeThreshold | 直接晉升到老年代對(duì)象的大小,設(shè)置這個(gè)參數(shù)后,大于這個(gè)參數(shù)的對(duì)象將直接在老年代分配 |
-XX:MaxTenuringThreshold | 晉升到老年代的對(duì)象年齡,每次Minor GC之后,年齡就加1,當(dāng)超過(guò)這個(gè)參數(shù)的值時(shí)進(jìn)入老年代 |
-XX:UseAdaptiveSizePolicy | 動(dòng)態(tài)調(diào)整java堆中各個(gè)區(qū)域的大小以及進(jìn)入老年代的年齡 |
-XX:+HandlePromotionFailure | 是否允許新生代收集擔(dān)保,進(jìn)行一次minor gc后, 另一塊Survivor空間不足時(shí),將直接會(huì)在老年代中保留 |
-XX:ParallelGCThreads | 設(shè)置并行GC進(jìn)行內(nèi)存回收的線程數(shù) |
-XX:GCTimeRatio | GC時(shí)間占總時(shí)間的比列,默認(rèn)值為99,即允許1%的GC時(shí)間,僅在使用Parallel Scavenge 收集器時(shí)有效 |
-XX:MaxGCPauseMillis | 設(shè)置GC的最大停頓時(shí)間,在Parallel Scavenge 收集器下有效 |
-XX:CMSInitiatingOccupancyFraction | 設(shè)置CMS收集器在老年代空間被使用多少后出發(fā)垃圾收集,默認(rèn)值為68%,僅在CMS收集器時(shí)有效,-XX:CMSInitiatingOccupancyFraction=70 |
-XX:+UseCMSCompactAtFullCollection | 由于CMS收集器會(huì)產(chǎn)生碎片,此參數(shù)設(shè)置在垃圾收集器后是否需要一次內(nèi)存碎片整理過(guò)程,僅在CMS收集器時(shí)有效 |
-XX:+CMSFullGCBeforeCompaction | 設(shè)置CMS收集器在進(jìn)行若干次垃圾收集后再進(jìn)行一次內(nèi)存碎片整理過(guò)程,通常與UseCMSCompactAtFullCollection參數(shù)一起使用 |
-XX:+UseFastAccessorMethods | 原始類型優(yōu)化 |
-XX:+DisableExplicitGC | 是否關(guān)閉手動(dòng)System.gc |
-XX:+CMSParallelRemarkEnabled | 降低標(biāo)記停頓 |
-XX:LargePageSizeInBytes | 內(nèi)存頁(yè)的大小不可設(shè)置過(guò)大,會(huì)影響Perm的大小,-XX:LargePageSizeInBytes=128m |
Sun/oracle JDK GC組合方式
- | 新生代GC方式 | 老年代和持久代GC方式 |
---|---|---|
-XX:+UseSerialGC | Serial 串行GC | Serial Old 串行GC |
-XX:+UseParallelGC | Parallel Scavenge 并行回收GC | Serial Old 并行GC |
-XX:+UseConcMarkSweepGC | ParNew 并行GC | CMS并發(fā)GC, 當(dāng)出現(xiàn)“Concurrent Mode Failure”時(shí)采用Serial Old 串行GC |
-XX:+UseParNewGC | ParNew 并行GC | Serial Old 串行GC |
-XX:+UseParallelOldGC | Parallel Scavenge 并行回收GC | Parallel Old 并行GC |
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC | Serial 串行GC | CMS 并發(fā)GC 當(dāng)出現(xiàn)“Concurrent Mode Failure”時(shí)采用Serial Old 串行GC |
參考文章:
理解Java垃圾回收機(jī)制(1)
理解Java垃圾回收機(jī)制(2)
深入理解JVM : Java垃圾收集器
如果想加入Android技術(shù)交流群,請(qǐng)長(zhǎng)按識(shí)別二維碼關(guān)注下方公眾號(hào),點(diǎn)擊“加群”獲取加群方式。