上一篇JVM垃圾收集器與內(nèi)存分配策略(一),下面是jdk1.7版本的垃圾收集器之間的關系,其中連線兩端的兩種垃圾收集器可以進行搭配使用丽蝎,下面來總結(jié)一下這些收集器的一些特點以及關系欣舵。
一止剖、Serial收集器
1、serial收集器是一個單線程的收集器馍盟,單線程說明兩點:①只會使用一個CPU或者一條線程來完成垃圾收集的工作于置;②在進程垃圾收集的時候,必須暫停掉其他所有的工作線程(Stop The World)贞岭,直到收集結(jié)束八毯。這項收集的工作是虛擬機在用戶不可見的情況下將其正常工作的線程停掉,然后在后臺進行自動發(fā)起收集和完成收集瞄桨。這對于用戶體驗而言是不佳的话速,但是考慮到防止在收集的時候用戶程序又在產(chǎn)生垃圾,這樣的話效果不好芯侥。
2泊交、Serial收集器是虛擬機運行在Client模式下的默認新生代收集器,它簡單高效柱查,對于限定在單個CPU的環(huán)境中因為不存在線程切換交互的開銷廓俭,單對于垃圾收集而言能夠活著很高的單線程效率。在用戶桌面應用程序中唉工,分配給虛擬機管理的內(nèi)存一般不會很大研乒,對于使用少量內(nèi)存的新生代而言,停頓時間一般控制在幾十毫秒活著一百多毫秒淋硝,發(fā)生的也不頻繁所以是可以接受的雹熬。
二宽菜、ParNew收集器
1、上面說到Serial收集器是單線程的版本竿报,而ParNew可以說就是Serial收集器的多線程版本铅乡,下面是ParNew收集器的簡單工作流程
2、ParNew收集器是運行在Server模式下面的虛擬機首選的新生代收集器仰楚,從最開始虛擬機之間搭配使用關系的時候可以看出隆判,只有ParNew能夠和CMS(后面會說到Concurrent Mark Sweep)搭配使用(對于Server模式)
3、我們也說到過僧界,ParNew收集器對于在單CPU環(huán)境下面侨嘀,由于存在線程交互的開銷,使用效果不會比Serial收集器好捂襟,當然隨著CPU數(shù)量的增加咬腕,對于GC時候系統(tǒng)資源的利用率提高和自身收集效率都是有很大好處。
三葬荷、Parallel Scavenge收集器
1涨共、Parallel Scavenge收集器也是一個新生代收集器,使用復制算法宠漩,采用并行多線程方式的收集器举反。
2、Parallel Scavenge收集器的特點在于:它想要達到一個可控制的吞吐量(運行用戶代碼時間/(運行用戶代碼時間+垃圾收集器運行時間))扒吁。對于停頓時間而言火鼻,其越短就越適合與用戶交互的程序,良好的響應速度能夠提升用戶體驗雕崩,較高的吞吐量就可以高效率的利用CPU時間魁索,完成程序執(zhí)行的任務。
3盼铁、Parallel Scavenge收集器提供下面兩個參數(shù)用于控制吞吐量
①-XX:MaxGCPauseMillis?用于設置最大垃圾收集停頓時間的參數(shù):MaxGCPauseMillis的數(shù)值是一個大于0的毫秒數(shù)粗蔚,垃圾收集器將盡可能保證在設置的時間內(nèi)完成工作。這里要注意的是:GC停頓時間是以犧牲吞吐量和新生代空間來換取的饶火,如果將新生代調(diào)小一些鹏控,那么垃圾收集也會變得更加頻繁。比如說將新生代空間由500M調(diào)整為300M肤寝,將收集頻率由10秒變?yōu)?s牧挣,相對而言收集變得更加頻繁,然后停頓時間由100ms變?yōu)?0ms醒陆,這樣的話雖然停頓時間縮短了,但是系統(tǒng)的吞吐量也變小了裆针。
②-XX:GCTimeRatio?直接設置吞吐量大小的參數(shù):其值是一個>0且<100的整數(shù)刨摩,也就是垃圾收集時間占總時間的比例寺晌。比如將參數(shù)設置為19,那么允許的最大GC時間就是總時間的5%=1/(1+19)澡刹,默認值是99%=1/(1+99)
4呻征、Parallel Scavenge收集器也被稱為吞吐量優(yōu)先的收集器,除了上面的參數(shù)之外罢浇,它還提供一種自適應策略陆赋,
+XX:UserAdapterSizePolicy:這是一個開關參數(shù),當這個參數(shù)打開之后嚷闭,不需要手動指定新生代的大小(-Xmn)攒岛、Eden區(qū)和Survivor區(qū)的比例、晉升老年代對象年齡大小等參數(shù)了胞锰,虛擬機將會根據(jù)當前系統(tǒng)的運行情況收集性能控制信息動態(tài)調(diào)整這些參數(shù)灾锯。
四、Serial Old收集器
1嗅榕、Serial Old收集器是Serial的老年代版本顺饮,同樣是一個單線程的收集器,采用標記-整理的算法進行收集凌那,主要也是在Client模式下面使用
2兼雄、下面是Serial Old的簡單工作流程
五、Parallel Old收集器
1帽蝶、Parallel Old是Parallel Scavenge收集器的老年代版本赦肋,使用多線程和標記-整理算法來進行實現(xiàn)
2、下面是Parallel Old收集器的工作流程
3嘲碱、在最開始介紹各種收集器之間搭配使用的時候金砍,我們可以看到,在Parallel Old收集器出現(xiàn)之前麦锯,Parallel Scavenge收集器就只能和單線程的Serial Old收集器搭配使用恕稠,而Serial Old在Server應用性能上面比較低(多CPU情況下無法充分利用多CPU的處理能力),所以原本注重吞吐量的Parallel Scavenge收集器就不能充分發(fā)揮作用扶欣。而使用Parallel Old+Parallel Scavenge收集器則可以充分發(fā)揮多CPU的處理能力鹅巍,在吞吐量的提高上面比較客觀。
六料祠、CMS收集器
1骆捧、CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓回收時間為目標的收集器。對于服務器響應速度快髓绽、系統(tǒng)停頓時間短的要求能夠很好的滿足敛苇。
2、CMS收集器是基于標記-清除的算法來進行實現(xiàn)的顺呕,其工作過程分為下面四個步驟
》闩省①初始標記:需要Stop The World括饶,該階段僅僅是標記一些GC Roots能夠直接關聯(lián)到的對象,
±凑恰②并發(fā)標記:并發(fā)標記階段就是GC Tracing的過程图焰,標記與GC Roots間接關聯(lián)的對象,不需要Stop The World
”钠③重新標記:修正并發(fā)標記階段因用戶程序繼續(xù)執(zhí)行而導致標記產(chǎn)生變動的那一部分對象技羔,這一階段的時間開銷會比初始標記稍長,但是會比并發(fā)標記短
∥钥埂④并發(fā)清除:進行垃圾收集
3藤滥、下面是CMS收集器的工作流程:其中耗時比較長的并發(fā)標記和并發(fā)清除都可以和用戶程序一起執(zhí)行,采用并發(fā)的方式減少了時間停頓
4颗味、下面是CMS收集器的缺點
〕健①由于他的并發(fā)性,導致其對于CPU的資源比較敏感浦马,如果硬件資源不夠时呀,那么CMS收集器的效率會很低;除此之外晶默,雖然CMS工作時候不會導致用戶程序停頓谨娜,但是會占用CPU的一部分資源導致用戶程序忽然變慢,總的吞吐量會降低磺陡。
∨可摇②CMS收集器無法處理浮動垃圾,可能會出現(xiàn)并發(fā)標記失敗而提前出發(fā)一次Full GC币他。由于CMS并發(fā)清理階段用戶程序還在繼續(xù)運行坞靶,就會產(chǎn)生新的垃圾對象,這一部分出現(xiàn)在標記過程之后蝴悉,CMS無法在當次收集的過程中處理掉彰阴,就只能留待下一次進行處理,這些就是浮動垃圾拍冠。
∧蛘狻③CMS收集結(jié)束會產(chǎn)生大量空間碎片。CMS是基于標記-清除算法來進行實現(xiàn)的收集器庆杜,這種收集算法就會導致比較多的空間碎片射众,而且在無法找到足夠的連續(xù)空間來分配當前對象的時候,就會提前觸發(fā)一次Full GC晃财。CMS收集器提供-XX:+UseCMSCompactAtFullCollection參數(shù)用于不得不觸發(fā)Full GC的時候進行內(nèi)存碎片的整理合并(但是這種方式的缺點也很顯然:內(nèi)存整理的過程是無法并發(fā)的叨橱,就會導致停頓時間變長)。
七、G1收集器
1罗洗、G1收集器的特點:
〖慰恪①并行與并發(fā):CPU利用多CPU、多個核的特點栖博,使用多個CPU來縮短Stop The? World的時間
②分代收集:G1采用不同與其他方式去處理新創(chuàng)建的對象和已經(jīng)存活一段時間(熬過多次GC)的對象
∠岫础③空間整合:G1整體上采用的是標記-整理算法來實現(xiàn)仇让,局部上看則是復制算法實現(xiàn),而這兩種算法都不會產(chǎn)生空間碎片
④可預測的停頓:G1除了降低停頓之外躺翻,還建立可預測的停頓時間模型丧叽,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi),控制消耗在垃圾收集上面的時間不超過N秒
2公你、在G1收集器的眼里踊淳,它將Java堆分為多個大小相等的區(qū)域(Region),雖然還保留有新生代和老年代的概念陕靠,但是都是一個Region的集合迂尝,不再是物理隔離的了。
3剪芥、G1能夠建立可預測的停頓垄开,是因為能有計劃的避免在Java堆中進行全區(qū)域的垃圾收集。G1追蹤各個Region里面的垃圾堆積回收后所獲得空間大小以及回收所需要的時間值?(作為一個回收價值參考税肪,然后在后臺建立一個優(yōu)先列表溉躲,然后在收集的時候根據(jù)允許的收集時間優(yōu)先回收價值最大的Region。
4益兄、關于G1的化整為零思想
實際上锻梳,Region的實現(xiàn)復雜:考慮將Java堆分為多個Region后,垃圾收集不一定就是按照想要的按照Region為單位的方式進行執(zhí)行净捅,因為Region不是孤立的疑枯。當一個對象分配在某一個Region中,該對象不是只能夠被本Region中的對象進行引用灸叼,而是可以和整個Java堆中任意的對象發(fā)生引用關系(換句話說神汹,不同的Region之間其中的對象總是可能存在引用關系)。這樣的問題不止存在于G1收集器中古今,在其他的收集器中(新生代中的對象可能和老年代的對象有引用關系)同樣存在屁魏,那么GC的時候效率就會降低很多(掃描整個Java堆)。
在G1和其他分代收集器中捉腥,處理不同區(qū)域之間的對象引用的方式是這樣的:使用Remember Set來避免全堆掃描氓拼。G1中每個Region都有一個與之對應的Remember Set,虛擬機在發(fā)現(xiàn)對Reference 類型的數(shù)據(jù)進行Write操作的時候,會首先觸發(fā)一個Write Barrier中斷操作桃漾,去檢查該Reference對象是否在其他Region存在引用(在其他分代收集器中就是檢查老年代中的對象是否引用了新生代中的對象)坏匪,如果存在的話就會將相關引用信息記錄到被引用對象所屬的Region的Remember Set中。這樣在進行內(nèi)存回收的時候撬统,在GCRoot的枚舉范圍之內(nèi)加入Remember Set就能保證不對整個Java堆掃描也不會遺漏存在引用關系的對象适滓。
5、下面是G1收集器的簡單工作流程
×底贰①初始標記:僅僅標記一些能與GC Roots之間關聯(lián)到的對象凭迹,并且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發(fā)執(zhí)行的時候苦囱,能在正確可用的Region中創(chuàng)建對象(需要Stop The World)
⌒岢瘛②并發(fā)標記:從GC Roots開始進行可達性分析,找出存活的對象撕彤,這一階段用戶線程的執(zhí)行可能會改變引用關系鱼鸠,JVM會將標記變動記錄線程的Remember Set Logs里面,在最終標記階段合并到Remember Set中
「Α③最終標記:修正在并發(fā)標記階段因為用戶程序繼續(xù)執(zhí)行導致標記產(chǎn)生變動的那一部分標記記錄蚀狰,并發(fā)標記階段中Remember Set Logs中的數(shù)據(jù)合并到Remember Set中,需要Stop The? World睦裳,可以并行執(zhí)行
≡旃④篩選回收:首先對各個Region的回收價值和成本進行排序,根據(jù)所期望的GC停頓時間來指定回收計劃(可以和用戶程序一起并發(fā)執(zhí)行)
6廉邑、優(yōu)點
「缥怠①與CMS收集器相比:不會產(chǎn)生空間碎片(按照Region為單位進行回收,并且采用的是標記整理算法來實現(xiàn))蛛蒙;并發(fā)階段可以為工作線程預留足夠的空間(單獨分配空閑的Region空間提供使用)糙箍;有可以預測的停頓時間
八、內(nèi)存分配與回收策略
1牵祟、對象優(yōu)先在Eden區(qū)上分配
a)關于Minor GC和Full GC
∩詈弧①Minor GC:新生代GC,指發(fā)生在新生代的垃圾收集動作诺苹,因為Java新生對象大多數(shù)具有朝生夕滅的特點咕晋,所以新生代GC發(fā)生的比較頻繁,回收速度也比較快
∈毡肌②Full GC:老年代GC掌呜,也叫MajorGC;發(fā)生在老年代坪哄,一般回收速度比Minor GC慢10倍质蕉。
b)一般情況下势篡,對象優(yōu)先在Eden區(qū)分配,當Eden區(qū)沒有足夠的空間的時候模暗,將會觸發(fā)一次Minor GC禁悠;
c)下面使用一個簡單的例子,然后通過-XX:+PrintGCDetails查看GC日志信息
1package cn.jvm.test; 2 3publicclass Test08 { 4privatestaticfinalinttest = 1024 * 1024; 5//-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 6publicstaticvoid main(String[] args) { 7byte[] t1, t2, t3, t4; 8t1 =newbyte[2 * test]; 9t2 =newbyte[2 * test];10t3 =newbyte[2 * test];//這個時候Eden區(qū)已經(jīng)分配了6M內(nèi)存兑宇,而Survivor區(qū)只有1M大小11t4 =newbyte[4 * test];12? ? }13}
“臁①上面的例子中,我們使用-Xms20M設置堆空間初始大小為20M隶糕, -Xmx20M設置堆空間最大為20M祝钢, -Xmn10M設置新生代大小為10M, -XX:+PrintGCDetails 打印GC日志信息若厚,-XX:SurvivorRatio=8設置新生代中Eden:From:To = 8:1:1
②下面是GC日志信息
2蜒什、大對象直接進入老年代
a)大對象就是指那些需要連續(xù)內(nèi)存空間的Java對象测秸,典型的就是很長的字符串或者數(shù)組(盡量避免使用朝生夕滅的大對象,因為會使虛擬機不得不提前出發(fā)GC找到足夠的連續(xù)空間進行分配)
b)虛擬機提供-XX:PretenureSizeThreshold參數(shù)灾常,用于設置大于這個值的時候?qū)ο笾苯釉诶夏甏M行分配(避免在Eden區(qū)和Survivor區(qū)發(fā)生大量的內(nèi)存復制)
c)下面是一個簡單的例子霎冯,和打印的GC日志信息
1package cn.jvm.test;23publicclass Test09 {4publicstaticvoid main(String[] args) {5byte[] test =newbyte[6 * 1024 * 1024];6? ? }7}
①上面的例子中钞瀑,我們使用-XX:PretenureSizeThreshold=31400000設置大于這個值的時候沈撞,分配對象直接分配在老年代
②下面是GC日志信息
3雕什、長期存活的對象直接進入老年代
a)虛擬為每個分配的對象定義了一個對象年齡計數(shù)器缠俺,如果新生對象在Eden區(qū)并經(jīng)過一次GC后被成功復制到Survivor區(qū)之后,就會將其對象年齡設置為1贷岸,之后每次熬過一次GC都會將對象年齡加1壹士,當年齡達到一定的大小(默認15)的時候偿警,就會進入老年代躏救。
b)虛擬機提供-XX:MaxTenuringThreshold參數(shù)設置年齡的閾值
4、動態(tài)對象年齡判定
除了按照對象的年齡來判斷對象是否需要進入老年代螟蒸,在實際當中盒使,并不是硬性要求對象的年齡必須達到XX:MaxTenuringThreshold設置的值才會進入老年代,通常情況下:當Survivor空間中所有同齡對象的大小總和超過了空間的一半七嫌,那么年齡大于等于該年齡的對象就直接分配在老年代
5少办、空間分配擔保機制
a)首先,我們需要知道新生代都采用復制算法抄瑟,當Survivor空間不夠存放存活的對象的時候凡泣,就需要老年代進行分配擔保枉疼。如果老年代的剩余空間大于新生代所有對象之和,那么擔保是沒有問題的鞋拟,但是如果新生代的所有存活對象都大于Survivor骂维,那么這些對象就會因為進入老年代導致老年代空間不足而觸發(fā)FullGC,此時的老年代擔保是有風險的贺纲。
b)對于這個風險的問題航闺,JVM會查看參數(shù)HandlePromotionFailure參數(shù)(是否允許冒險)的設置,如果不允許就會直接進行FullGC猴誊,如果允許的話就會先查看一下每次進入老年代的對象大小之和的平均值(如果老年代的剩余空間大小大于這個平均值潦刃,就認為可以冒險),但是如果在冒險進行Minor GC的時候發(fā)現(xiàn)新生代100%對象都存活(這是一種極端情況)懈叹,那么還是會進行FullGC乖杠。看起來擔保失敗的時候繞了比較大的彎子澄成,但是為了避免FullGC的頻繁出現(xiàn)還是會將這種冒險的行為設置為true(JDK6Update24之后的默認設置)