1.簡要闡述JVM的CMS GC算法和JVM的G1 GC算法的基本原理彼硫。
?gc就是java的垃圾回收機(jī)制(gabage collection)
學(xué)習(xí)gc之前,要知道一個單詞:stop the world 呢袱。它會在任何一個gc算法中發(fā)生。gvm執(zhí)行g(shù)c會停止應(yīng)用程序的執(zhí)行,在gc執(zhí)行時會停止出了gc線程意外的所有線程扭弧,使其進(jìn)入等待狀態(tài)蕉陋,直到gc完成捐凭。gc優(yōu)化很多時候就是減少stop the world的發(fā)生。
gc只回收堆區(qū)和方法區(qū)的對象凳鬓,棧區(qū)的數(shù)據(jù)超出作用域后會被jvm會自動釋放掉茁肠。所以棧區(qū)的數(shù)據(jù)不在gc的管理中。
GC什么時候可以判斷對象可以被回收了:
1.對象沒有引用 2. 作用域發(fā)生未捕獲異常 3.程序在作用域正常執(zhí)行完畢 4.程序執(zhí)行了System.exit()
5.程序發(fā)生意外終止(被殺線程等)
在Java程序中不能顯式的分配和注銷緩存缩举,因?yàn)檫@些事情JVM都幫我們做了垦梆,那就是GC。
有些時候我們可以將相關(guān)的對象設(shè)置成null 來試圖顯示的清除緩存仅孩,但是并不是設(shè)置為null 就會一定被標(biāo)記為可回收托猩,有可能會發(fā)生逃逸。
將對象設(shè)置成null 至少沒有什么壞處辽慕,但是使用System.gc() 便不可取了京腥,使用System.gc() 時候并不是馬上執(zhí)行GC操作,而是會等待一段時間溅蛉,甚至不執(zhí)行公浪,而且System.gc() 如果被執(zhí)行,會觸發(fā)Full GC 船侧,這非常影響性能欠气。
按代的垃圾回收機(jī)制:
新生代(Young generation):絕大多數(shù)最新被創(chuàng)建的對象都會被分配到這里,由于大部分在創(chuàng)建后很快變得不可達(dá)勺爱,很多對象被創(chuàng)建在新生代晃琳,然后“消失”。對象從這個區(qū)域“消失”的過程我們稱之為:Minor GC 琐鲁。
老年代(Old generation):對象沒有變得不可達(dá)卫旱,并且從新生代周期中存活了下來,會被拷貝到這里围段。其區(qū)域分配的空間要比新生代多顾翼。也正由于其相對大的空間,發(fā)生在老年代的GC次數(shù)要比新生代少得多奈泪。對象從老年代中消失的過程适贸,稱之為:Major GC 或者 Full GC灸芳。
持久代(Permanent generation):也稱之為 方法區(qū)(Method area):用于保存類常量以及字符串常量。注意拜姿,這個區(qū)域不是用于存儲那些從老年代存活下來的對象烙样,這個區(qū)域也可能發(fā)生GC。發(fā)生在這個區(qū)域的GC事件也被算為 Major GC 蕊肥。只不過在這個區(qū)域發(fā)生GC的條件非常嚴(yán)苛谒获,必須符合以下三種條件才會被回收:
1、所有實(shí)例被回收
2壁却、加載該類的ClassLoader 被回收
3批狱、Class 對象無法通過任何途徑訪問(包括反射)
如果老年代的對象需要引用新生代的對象,會發(fā)生什么呢展东?
為了解決這個問題赔硫,老年代中存在一個 card table ,它是一個512byte大小的塊盐肃。所有老年代的對象指向新生代對象的引用都會被記錄在這個表中爪膊。當(dāng)針對新生代執(zhí)行GC的時候,只需要查詢 card table 來決定是否可以被回收恼蓬,而不用查詢整個老年代惊完。這個 card table 由一個write barrier 來管理。write barrier給GC帶來了很大的性能提升处硬,雖然由此可能帶來一些開銷小槐,但完全是值得的。
默認(rèn)的新生代(Young generation)荷辕、老年代(Old generation)所占空間比例為 1 : 2 凿跳。
JVM GC什么時候執(zhí)行?
eden區(qū)空間不夠存放新對象的時候疮方,執(zhí)行Minro GC控嗜。升到老年代的對象大于老年代剩余空間的時候執(zhí)行Full GC,或者小于的時候被HandlePromotionFailure 參數(shù)強(qiáng)制Full GC 骡显。調(diào)優(yōu)主要是減少 Full GC 的觸發(fā)次數(shù)疆栏,可以通過 NewRatio 控制新生代轉(zhuǎn)老年代的比例,通過MaxTenuringThreshold 設(shè)置對象進(jìn)入老年代的年齡閥值惫谤。
新生代空間的構(gòu)成與邏輯
為了更好的理解GC壁顶,我們來學(xué)習(xí)新生代的構(gòu)成,它用來保存那些第一次被創(chuàng)建的對象溜歪,它被分成三個空間:
一個伊甸園空間(Eden)
兩個幸存者空間(Fron Survivor若专、To Survivor)
默認(rèn)新生代空間的分配: ? ? ? ? Eden : Fron : To = 8 : 1 : 1
每個空間的執(zhí)行順序如下:
1、絕大多數(shù)剛剛被創(chuàng)建的對象會存放在伊甸園空間(Eden)蝴猪。
2调衰、在伊甸園空間執(zhí)行第一次GC(Minor GC)之后膊爪,存活的對象被移動到其中一個幸存者空間(Survivor)。
3嚎莉、此后米酬,每次伊甸園空間執(zhí)行GC后,存活的對象會被堆積在同一個幸存者空間趋箩。
4淮逻、當(dāng)一個幸存者空間飽和,還在存活的對象會被移動到另一個幸存者空間阁簸。然后會清空已經(jīng)飽和的哪個幸存者空間。
5哼丈、在以上步驟中重復(fù)N次(N = MaxTenuringThreshold(年齡閥值設(shè)定启妹,默認(rèn)15))依然存活的對象,就會被移動到老年代醉旦。
從上面的步驟可以發(fā)現(xiàn)饶米,兩個幸存者空間,必須有一個是保持空的车胡。如果兩個兩個幸存者空間都有數(shù)據(jù)檬输,或兩個空間都是空的,那一定是你的系統(tǒng)出現(xiàn)了某種錯誤匈棘。
我們需要重點(diǎn)記住的是丧慈,對象在剛剛被創(chuàng)建之后,是保存在伊甸園空間的(Eden)主卫。那些長期存活的對象會經(jīng)由幸存者空間(Survivor)轉(zhuǎn)存到老年代空間(Old generation)逃默。
也有例外出現(xiàn),對于一些比較大的對象(需要分配一塊比較大的連續(xù)內(nèi)存空間)則直接進(jìn)入到老年代簇搅。一般在Survivor 空間不足的情況下發(fā)生完域。
老年代空間的構(gòu)成與邏輯:
老年代空間的構(gòu)成其實(shí)很簡單,它不像新生代空間那樣劃分為幾個區(qū)域瘩将,它只有一個區(qū)域吟税,里面存儲的對象并不像新生代空間絕大部分都是朝聞道,夕死矣姿现。這里的對象幾乎都是從Survivor 空間中熬過來的肠仪,它們絕不會輕易的狗帶。因此建钥,F(xiàn)ull GC(Major GC)發(fā)生的次數(shù)不會有Minor GC 那么頻繁藤韵,并且做一次Major GC 的時間比Minor GC 要更長(約10倍)。
JVM GC 算法講解:
1熊经、根搜索算法
根搜索算法是從離散數(shù)學(xué)中的圖論引入的泽艘,程序把所有引用關(guān)系看作一張圖欲险,從一個節(jié)點(diǎn)GC ROOT 開始,尋找對應(yīng)的引用節(jié)點(diǎn)匹涮,找到這個節(jié)點(diǎn)后天试,繼續(xù)尋找這個節(jié)點(diǎn)的引用節(jié)點(diǎn)。當(dāng)所有的引用節(jié)點(diǎn)尋找完畢后然低,剩余的節(jié)點(diǎn)則被認(rèn)為是沒有被引用到的節(jié)點(diǎn)喜每,即無用的節(jié)點(diǎn)。
上圖紅色為無用的節(jié)點(diǎn)雳攘,可以被回收带兜。
目前Java中可以作為GC ROOT的對象有:
1、虛擬機(jī)棧中引用的對象(本地變量表)
2吨灭、方法區(qū)中靜態(tài)屬性引用的對象
3刚照、方法區(qū)中常亮引用的對象
4、本地方法棧中引用的對象(Native對象)
基本所有GC算法都引用根搜索算法這種概念喧兄。
2无畔、標(biāo)記 - 清除算法
標(biāo)記-清除算法采用從根集合進(jìn)行掃描,對存活的對象進(jìn)行標(biāo)記吠冤,標(biāo)記完畢后浑彰,再掃描整個空間中未被標(biāo)記的對象進(jìn)行直接回收,如上圖拯辙。
標(biāo)記-清除算法不需要進(jìn)行對象的移動郭变,并且僅對不存活的對象進(jìn)行處理,在存活的對象比較多的情況下極為高效薄风,但由于標(biāo)記-清除算法直接回收不存活的對象饵较,并沒有對還存活的對象進(jìn)行整理,因此會導(dǎo)致內(nèi)存碎片遭赂。
3循诉、復(fù)制算法
復(fù)制算法將內(nèi)存劃分為兩個區(qū)間,使用此算法時撇他,所有動態(tài)分配的對象都只能分配在其中一個區(qū)間(活動區(qū)間)茄猫,而另外一個區(qū)間(空間區(qū)間)則是空閑的。
復(fù)制算法采用從根集合掃描困肩,將存活的對象復(fù)制到空閑區(qū)間划纽,當(dāng)掃描完畢活動區(qū)間后,會的將活動區(qū)間一次性全部回收锌畸。此時原本的空閑區(qū)間變成了活動區(qū)間勇劣。下次GC時候又會重復(fù)剛才的操作,以此循環(huán)。
復(fù)制算法在存活對象比較少的時候比默,極為高效幻捏,但是帶來的成本是犧牲一半的內(nèi)存空間用于進(jìn)行對象的移動。所以復(fù)制算法的使用場景命咐,必須是對象的存活率非常低才行篡九,而且最重要的是,我們需要克服50%內(nèi)存的浪費(fèi)醋奠。
4榛臼、標(biāo)記 - 整理算法
標(biāo)記-整理算法采用 標(biāo)記-清除 算法一樣的方式進(jìn)行對象的標(biāo)記、清除窜司,但在回收不存活的對象占用的空間后沛善,會將所有存活的對象往左端空閑空間移動,并更新對應(yīng)的指針塞祈。標(biāo)記-整理 算法是在標(biāo)記-清除 算法之上路呜,又進(jìn)行了對象的移動排序整理,因此成本更高织咧,但卻解決了內(nèi)存碎片的問題。
JVM為了優(yōu)化內(nèi)存的回收漠秋,使用了分代回收的方式笙蒙,對于新生代內(nèi)存的回收(Minor GC)主要采用復(fù)制算法。而對于老年代的回收(Major GC)庆锦,大多采用標(biāo)記-整理算法捅位。
與垃圾回收相關(guān)的JVM參數(shù):
-Xms / -Xmx — 堆的初始大小 / 堆的最大大小
-Xmn — 堆中年輕代的大小
-XX:-DisableExplicitGC — 讓System.gc()不產(chǎn)生任何作用
-XX:+PrintGCDetails — 打印GC的細(xì)節(jié)
-XX:+PrintGCDateStamps — 打印GC操作的時間戳
-XX:NewSize / XX:MaxNewSize — 設(shè)置新生代大小/新生代最大大小
-XX:NewRatio — 可以設(shè)置老生代和新生代的比例
-XX:PrintTenuringDistribution — 設(shè)置每次新生代GC后輸出幸存者樂園中對象年齡的分布
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設(shè)置老年代閥值的初始值和最大值
-XX:TargetSurvivorRatio:設(shè)置幸存區(qū)的目標(biāo)使用率。
(之前是廢話搂抒,之后也有可能是廢話艇搀,反正都是到處抄點(diǎn))
CMS算法(老年代并發(fā)收集器):
-XX:+UseConcMarkSweepGC
新生代:復(fù)制算法,默認(rèn)搭配ParNewGC(ParNew其實(shí)就是Serial收集器的多線程版本求晶。除了Serial收集器外焰雕,只有它能與CMS收集器配合工作),并行
年老代:標(biāo)記-清除芳杏,并發(fā)(如果發(fā)生Concurrent Mode Fail矩屁,則使用SerialOld(SerialOld是Serial收集器的老年代收集器版本,它同樣是一個單線程收集器)做后備收集器)
初始標(biāo)記 :單線程爵赵;在這個階段吝秕,需要虛擬機(jī)停頓正在執(zhí)行的任務(wù),官方的叫法STW(Stop The Word)空幻。這個過程從垃圾回收的"根對象"開始烁峭,只掃描到能夠和"根對象"直接關(guān)聯(lián)的對象,并作標(biāo)記秕铛。所以這個過程雖然暫停了整個JVM约郁,但是很快就完成了缩挑。
并發(fā)標(biāo)記 :是CMS最主要的工作階段這個階段;緊隨初始標(biāo)記階段棍现,在初始標(biāo)記的基礎(chǔ)上繼續(xù)向下追溯標(biāo)記调煎。并發(fā)標(biāo)記階段,應(yīng)用程序的線程和并發(fā)標(biāo)記的線程并發(fā)執(zhí)行己肮,所以用戶不會感受到停頓士袄。
并發(fā)預(yù)清理 :并發(fā)預(yù)清理階段仍然是并發(fā)的。在這個階段谎僻,虛擬機(jī)查找在執(zhí)行并發(fā)標(biāo)記階段新進(jìn)入老年代的對象(可能會有一些對象從新生代晉升到老年代娄柳, 或者有一些對象被分配到老年代)。通過重新掃描艘绍,減少下一個階段"重新標(biāo)記"的工作赤拒,因?yàn)橄乱粋€階段會Stop The World。
重新標(biāo)記 :掃描從"根對象"開始向下追溯诱鞠,并處理對象關(guān)聯(lián)挎挖。由于應(yīng)用程序還在并發(fā)運(yùn)行產(chǎn)生的對象的修改,多線程航夺,速度快蕉朵,需要全局停頓
并發(fā)清理 :清理垃圾對象,這個階段收集器線程和應(yīng)用程序線程并發(fā)執(zhí)行阳掐。
并發(fā)重置 :這個階段始衅,重置CMS收集器的數(shù)據(jù)結(jié)構(gòu),等待下一次垃圾回收缭保。
CMS缺點(diǎn):
1汛闸、內(nèi)存碎片。由于使用了 標(biāo)記-清理 算法艺骂,導(dǎo)致內(nèi)存空間中會產(chǎn)生內(nèi)存碎片诸老。不過CMS收集器做了一些小的優(yōu)化,就是把未分配的空間匯總成一個列表钳恕,當(dāng)有JVM需要分配內(nèi)存空間的時候孕锄,會搜索這個列表找到符合條件的空間來存儲這個對象。但是內(nèi)存碎片的問題依然存在苞尝,如果一個對象需要3塊連續(xù)的空間來存儲畸肆,因?yàn)閮?nèi)存碎片的原因,尋找不到這樣的空間宙址,就會導(dǎo)致Full GC轴脐。
2、需要更多的CPU資源。由于使用了并發(fā)處理大咱,很多情況下都是GC線程和應(yīng)用線程并發(fā)執(zhí)行的恬涧,這樣就需要占用更多的CPU資源,也是犧牲了一定吞吐量的原因碴巾。
3溯捆、需要更大的堆空間。因?yàn)镃MS標(biāo)記階段應(yīng)用程序的線程還是執(zhí)行的厦瓢,那么就會有堆空間繼續(xù)分配的問題提揍,為了保障CMS在回收堆空間之前還有空間分配給新加入的對象,必須預(yù)留一部分空間煮仇。
并發(fā)模式失斃驮尽(Concurrent Mode Failure)
? ? 并發(fā)GC,吞吐量下降浙垫,采用標(biāo)記清除刨仑,碎片多,占用額外內(nèi)存夹姥,不能在堆空間滿時清理杉武,觸發(fā)GC,清理時辙售,應(yīng)用程序還在運(yùn)行此時如果預(yù)留的空間不夠應(yīng)用程序申請的空間的話艺智,則會觸發(fā)Concurrent Mode Fail,此時便會啟用后備收集器:SerialOld進(jìn)行GC圾亏,產(chǎn)生全局停頓
浮動垃圾
由于CMS并發(fā)清理階段用戶線程還在運(yùn)行著,伴隨程序運(yùn)行自然就還會有新的垃圾不斷產(chǎn)生封拧,這一部分垃圾出現(xiàn)在標(biāo)記過程之后志鹃,CMS無法在當(dāng)次收集中處理掉它們,只好留待下一次GC時再清理掉泽西。這一部分垃圾就稱為“浮動垃圾”曹铃。
CMSInitiatingOccupancyFraction
由于在垃圾收集階段用戶線程還需要運(yùn)行,那也就還需要預(yù)留有足夠的內(nèi)存空間給用戶線程使用捧杉,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進(jìn)行收集陕见,需要預(yù)留一部分空間提供并發(fā)收集時的程序運(yùn)作使用。
在JDK 1.5的默認(rèn)設(shè)置下味抖,CMS收集器當(dāng)老年代使用了68%的空間后就會被激活评甜,這是一個偏保守的設(shè)置,如果在應(yīng)用中老年代增長不是太快仔涩,可以適當(dāng)調(diào)高參數(shù)-XX:CMSInitiatingOccupancyFraction的值來提高觸發(fā)百分比忍坷,以便降低內(nèi)存回收次數(shù)從而獲取更好的性能,
在JDK 1.6中,CMS收集器的啟動閾值已經(jīng)提升至92%佩研。要是CMS運(yùn)行期間預(yù)留的內(nèi)存無法滿足程序需要柑肴,就會出現(xiàn)一次“Concurrent Mode Failure”失敗,這時虛擬機(jī)將啟動后備預(yù)案:臨時啟用Serial Old收集器來重新進(jìn)行老年代的垃圾收集旬薯,這樣停頓時間就很長了晰骑。
所以說參數(shù)-XX:CMSInitiatingOccupancyFraction設(shè)置得太高很容易導(dǎo)致大量“Concurrent Mode Failure”失敗,性能反而降低绊序。
CMS內(nèi)存整理
CMS是一款基于“標(biāo)記—清除”算法實(shí)現(xiàn)的收集器硕舆,這意味著收集結(jié)束時會有大量空間碎片產(chǎn)生。
空間碎片過多時政模,將會給大對象分配帶來很大麻煩岗宣,往往會出現(xiàn)老年代還有很大空間剩余,但是無法找到足夠大的連續(xù)空間來分配當(dāng)前對象淋样,不得不提前觸發(fā)一次Full GC耗式。
為了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關(guān)參數(shù)(默認(rèn)就是開啟的)趁猴,用于在CMS收集器頂不住要進(jìn)行FullGC時開啟內(nèi)存碎片的合并整理過程刊咳,內(nèi)存整理的過程是無法并發(fā)的,空間碎片問題沒有了儡司,但停頓時間不得不變長娱挨。
虛擬機(jī)設(shè)計者還提供了另外一個參數(shù)-XX:CMSFullGCsBeforeCompaction,這個參數(shù)是用于設(shè)置執(zhí)行多少次不壓縮的Full GC后捕犬,跟著來一次帶壓縮的(默認(rèn)值為0跷坝,表示每次進(jìn)入Full GC時都進(jìn)行碎片整理)。
Promotion Failure&Premature Promotion
過早提升(Premature Promotion)碉碉,MinorGC過程中柴钻,Survivor可能不足以容納Eden和另外一個Survivor中存活的對象,如果Survivor中的存活對象溢出垢粮,多余的對象將被移到年老代贴届。
在MinorGC過程中,如果年老代滿了無法容納更多的對象蜡吧,則MinorGC之后毫蚓,通常會進(jìn)行FullGC,這將導(dǎo)致遍歷整個java堆昔善,這稱為提升失斣恕(Promotion Failure)
啥時候用CMS
如果你的應(yīng)用程序?qū)νnD比較敏感,并且在應(yīng)用程序運(yùn)行的時候可以提供更大的內(nèi)存和更多的CPU(也就是硬件牛逼)君仆,那么使用CMS來收集會給你帶來好處柬批。還有啸澡,如果在JVM中,有相對較多存活時間較長的對象(老年代比較大)會更適合使用CMS氮帐。
G1算法
? 每個對象被分配到不同的格子嗅虏,隨后GC執(zhí)行。當(dāng)一個區(qū)域裝滿之后上沐,對象被分配到另一個區(qū)域皮服,并執(zhí)行GC。這中間不再有從新生代移動到老年代的三個步驟参咙。這個類型是為了替代CMS GC而被創(chuàng)建的龄广,因?yàn)镃MS GC在長時間持續(xù)運(yùn)作時會產(chǎn)生很多問題。
參考:blog.csdn.net/renfufei/article/details/41897113
參考:segmentfault.com/a/1190000004707217
? ? ? ? ? blog.csdn.net/aibisoft/article/details/27555793
? ? ? ? ? zhuanlan.zhihu.com/p/25539690
? ? ? ? ? www.importnew.com/1993.html
? ? ? ?