JVM调优系列:(四)GC垃圾回收
跟蹤收集算法:
復(fù)制(copying):將堆內(nèi)分成兩個(gè)相同空間,從根(ThreadLocal的對(duì)象,靜態(tài)對(duì)象)開(kāi)始訪問(wèn)每一個(gè)關(guān)聯(lián)的活躍對(duì)象,將空間A的活躍對(duì)象全部復(fù)制到空間B,然后一次性回收整個(gè)空間A。因?yàn)橹辉L問(wèn)活躍對(duì)象,將所有活動(dòng)對(duì)象復(fù)制走之后就清空整個(gè)空間,不用去訪問(wèn)死對(duì)象,不需要標(biāo)記驟,所以遍歷空間的成本較小,但需要巨大的復(fù)制成本和較多的內(nèi)存。
標(biāo)記清除(mark-sweep):收集器先從根開(kāi)始訪問(wèn)所有對(duì)象,標(biāo)記活躍對(duì)象。然后再遍歷一次整個(gè)內(nèi)存區(qū)域,把所有沒(méi)有標(biāo)記活躍的對(duì)象進(jìn)行回收處理。該算法遍歷整個(gè)空間的成本較大暫停時(shí)間隨空間大小線性增大,而且標(biāo)記結(jié)束后,相鄰的不可觸及對(duì)象所占空間會(huì)合并在一起,但不會(huì)進(jìn)行整理,將產(chǎn)生越來(lái)越多的碎片。
標(biāo)記整理(mark-sweep-compact):收集器先從根開(kāi)始訪問(wèn)所有對(duì)象,先標(biāo)記活躍對(duì)象,然后清除未標(biāo)記的對(duì)象,再將堆中活躍對(duì)象復(fù)制到堆的底部,使活躍對(duì)象緊湊的排列在一起,避免碎片產(chǎn)生。
?常用GC收集器類(lèi)型:
1.單CPU串行收集器(Serial Collector)使用 -XX:+UseSerialGC,策略為:
年輕代串行復(fù)制,-XX:MaxTenuringThreshold來(lái)設(shè)置對(duì)象復(fù)制的次數(shù)。
年老代串行標(biāo)記整理。
使用 -XX:+UseParallelGC ,也是JDK -server的默認(rèn)值, 它不能和CMS配合使用。策略為:
1.年輕代暫停應(yīng)用程序,多個(gè)垃圾收集線程并行的復(fù)制收集,線程數(shù)默認(rèn)為CPU個(gè)數(shù),CPU很多時(shí),可用–XX:ParallelGCThreads=線程數(shù)。
??? 2.年老代暫停應(yīng)用程序,與串行收集器一樣,單垃圾收集線程標(biāo)記整理。使用-XX:+UseParallelOldGC打開(kāi)年老代并行垃圾回收的線程數(shù).
可以使用-XX:MaxGCPauseMillis 和 -XX:GCTimeRatio 來(lái)調(diào)整GC的時(shí)間。
-XX:MaxGCPauseMillis=毫秒:指定垃圾回收時(shí)的最長(zhǎng)暫停時(shí)間,如果指定了此值的話,堆大小和垃圾回收相關(guān)參數(shù)會(huì)進(jìn)行調(diào)整以達(dá)到指定值。
-XX:GCTimeRatio :?吞吐量為垃圾回收時(shí)間與非垃圾回收時(shí)間的比值,公式為1/(1+N)。例如,-XX:GCTimeRatio=19時(shí),表示5%的時(shí)間用于垃圾回收。默認(rèn)情況為99,即1%的時(shí)間用于垃圾回收。
Parallel?Copying?Collector用-XX:UseParNewGC參數(shù)配置,它主要用于新生代的收集,此GC可以配合CMS一起使用。
Parallel?Mark-Compact?Collector用-XX:UseParallelOldGC參數(shù)配置,此GC主要用于老生代對(duì)象的收集(jdk1.6)。
??使用-XX:+UseConcMarkSweepGC, 主要用于老生代,策略為:
??? 1.年輕代同樣是暫停應(yīng)用程序,多個(gè)垃圾收集線程并行的復(fù)制收集。
??? 2.年老代則只有兩次短暫停,其他時(shí)間應(yīng)用程序與收集線程并發(fā)的清除。
采用兩次短暫停來(lái)替代標(biāo)記整理算法的長(zhǎng)暫停,它的收集周期:??初始標(biāo)記(CMS-initial-mark) -> 并發(fā)標(biāo)記(CMS-concurrent-mark) -> 重新標(biāo)記(CMS-remark)-> 并發(fā)清除(CMS-concurrent-sweep) ->并發(fā)重設(shè)狀態(tài)等待下次CMS的觸發(fā)(CMS-concurrent-reset)。
它的主要適合場(chǎng)景是對(duì)響應(yīng)時(shí)間的重要性需求大于對(duì)吞吐量的要求,能夠承受垃圾回收線程和應(yīng)用線程共享處理器資源,并且應(yīng)用中存在比較多的長(zhǎng)生命周期的對(duì)象的應(yīng)用。但CMS收集算法在最為耗時(shí)的內(nèi)存區(qū)域遍歷時(shí)采用多線程并發(fā)操作,但對(duì)于服務(wù)器CPU資源不夠的情況下,其實(shí)對(duì)性能是沒(méi)有提升的,反而會(huì)導(dǎo)致系統(tǒng)吞吐量的下降;
CMS默認(rèn)啟動(dòng)的回收線程數(shù)目是??(ParallelGCThreads+ 3)/4) ,可以通過(guò)來(lái)設(shè)定
-XX:ParallelGCThreads=N 來(lái)調(diào)整年輕代的并行收集線程數(shù), 年輕代的并行收集線程數(shù)默認(rèn)是(cpu <= 8) ? cpu : 3 +((cpu * 5) / 8).
-XX:ParallelCMSThreads=N調(diào)整CMS收集線程數(shù),CMS默認(rèn)啟動(dòng)的回收線程數(shù)目??(ParallelGCThreads+ 3)/4)
-XX:+UseCMSCompactAtFullCollectionCMS是不會(huì)整理堆碎片的,因此為了防止堆碎片引起full gc,通過(guò)會(huì)開(kāi)啟CMS階段進(jìn)行合并碎片選項(xiàng). 為了減少第二次暫停的時(shí)間,開(kāi)啟并行remark:-XX:+CMSParallelRemarkEnabled。如果remark還是過(guò)長(zhǎng)的話,可以開(kāi)啟-XX:+CMSScavengeBeforeRemark選項(xiàng),強(qiáng)制remark之前開(kāi)始一次minor gc,減少remark的暫停時(shí)間,但是在remark之后也將立即開(kāi)始又一次minor gc.
-XX:+CMSClassUnloadingEnabled-XX:+CMSPermGenSweepingEnabled一般情況下,持久代是不會(huì)進(jìn)行GC的,通過(guò)以上參數(shù)進(jìn)行強(qiáng)制設(shè)置。
-XX+UseCMSCompactAtFullCollection?在FULL GC的時(shí)候, 對(duì)年老代的壓縮
-XX:CMSFullGCsBeforeCompaction對(duì)年老代的壓縮開(kāi)啟的情況下,多少次FULL GC后進(jìn)行內(nèi)存壓縮,整理
-XX:+UseCMSInitiatingOccupancyOnly?指示只有在old generation在使用了初始化的比例后concurrentcollector啟動(dòng)收集
-XX:CMSInitiatingOccupancyFraction=80默認(rèn)CMS是在tenured generation沾滿68%的時(shí)候開(kāi)始進(jìn)行CMS收集, 如果你的年老代增長(zhǎng)不是那么快,并且希望降低CMS次數(shù)的話,可以適當(dāng)調(diào)高此值
-XX:+AggressiveHeap?試圖是使用大量的物理內(nèi)存,長(zhǎng)時(shí)間大內(nèi)存使用的優(yōu)化,CMS收集生效
-XX:+CMSIncrementalMode設(shè)置為增量模式, 單CPU情況使用
?
?
-XX:+UseAdaptiveSizePolicy?設(shè)置此選項(xiàng)后,并行收集器會(huì)自動(dòng)選擇年輕代區(qū)大小和相應(yīng)的Survivor區(qū)比例,以達(dá)到目標(biāo)系統(tǒng)規(guī)定的最低相應(yīng)時(shí)間或者收集頻率等,此值建議使用并行收集器時(shí),一直打開(kāi)。
-XX:MaxGCPauseMillis=100?設(shè)置每次年輕代垃圾回收的最長(zhǎng)時(shí)間,如果無(wú)法滿足此時(shí)間,JVM會(huì)自動(dòng)調(diào)整年輕代大小,以滿足此值
-XX:GCTimeRatio=19?設(shè)置垃圾回收時(shí)間占程序運(yùn)行時(shí)間的百分比,公式為1/(1+N),默認(rèn)情況為99,即1%的時(shí)間用于垃圾回收。
?
JVM GC機(jī)制:
在Java語(yǔ)言里面,可作為GC Roots的節(jié)點(diǎn)主要在全局性的引用(例如常量或類(lèi)靜態(tài)屬性)與執(zhí)行上下文(例如棧幀中的本地變量表)中。如果要使用可達(dá)性分析來(lái)判斷內(nèi)存是否可回收的,那分析工作必須在一個(gè)能保障一致性的快照中進(jìn)行——這里“一致性”的意思是整個(gè)分析期間整個(gè)執(zhí)行系統(tǒng)看起來(lái)就像被凍結(jié)在某個(gè)時(shí)間點(diǎn)上,不可以出現(xiàn)分析過(guò)程中,對(duì)象引用關(guān)系還在不斷變化的情況,這點(diǎn)不滿足的話分析結(jié)果準(zhǔn)確性就無(wú)法保證。這點(diǎn)也是導(dǎo)致GC進(jìn)行時(shí)必須暫停的其中一個(gè)重要原因.
HOTSPOT使用準(zhǔn)確式GC, 從外部記錄下類(lèi)型信息,存成映射表。HotSpot把這樣的數(shù)據(jù)結(jié)構(gòu)叫做OopMap,需要虛擬機(jī)里的解釋器和JIT編譯器都有相應(yīng)的支持,在類(lèi)加載完成的時(shí)候,HotSpot就把對(duì)象內(nèi)什么偏移量上是什么類(lèi)型的數(shù)據(jù)計(jì)算出來(lái),在JIT編譯過(guò)程中,也會(huì)在特定的位置記錄下棧里和寄存器里哪些位置是引用, 這樣GC在掃描時(shí)就就可以直接得知這些信息了。HotSpot是用“解釋式”的方式來(lái)使用OopMap的,每次都循環(huán)變量里面的項(xiàng)來(lái)掃描對(duì)應(yīng)的偏移量。
解釋式: 每次都遍歷原始的映射表,循環(huán)的一個(gè)個(gè)偏移量掃描過(guò)去,HOTSPOT采用
編譯式: 為每個(gè)映射表生成一塊定制的掃描代碼(想像掃描映射表的循環(huán)被展開(kāi)的樣子),以后每次要用映射表就直接執(zhí)行生成的掃描代碼。
?
在OopMap的協(xié)助下,HotSpot可以快速準(zhǔn)確地地完成GC Roots枚舉,但一個(gè)很現(xiàn)實(shí)的問(wèn)題隨之而來(lái),可能導(dǎo)致引用關(guān)系變化,或者說(shuō)OopMap內(nèi)容變化的指令非常多,如果為每一條指令都生成對(duì)應(yīng)的OopMap,那將會(huì)需要大量的額外空間,這樣GC的空間成本將會(huì)變得很高。
所以HotSpot也的確沒(méi)有為每條指令都生成OopMap,前面已經(jīng)提到,只是在“特定的位置”記錄了這些信息,這些位置被稱(chēng)為安全點(diǎn)(Safepoint),即程序執(zhí)行時(shí)并非在所有的地方都能停頓下來(lái)開(kāi)始GC,只有在到達(dá)安全點(diǎn)時(shí)才能暫停。
每個(gè)被JIT編譯過(guò)后的方法也會(huì)在安全點(diǎn)記錄下OopMap,記錄了執(zhí)行到該方法的某條指令的時(shí)候,棧上和寄存器里哪些位置是引用。這樣GC在掃描棧的時(shí)候就會(huì)查詢這些OopMap就知道哪里是引用了。Safepoint的選定既不能太少以至于讓GC等待時(shí)間太長(zhǎng),也不能過(guò)于頻繁以至于過(guò)分增大運(yùn)行時(shí)的負(fù)荷。所以安全點(diǎn)的選定基本上是以程序“是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征”為標(biāo)準(zhǔn)進(jìn)行選定:
1、循環(huán)的末尾
2、方法臨返回前 / 調(diào)用方法的call指令后
3、可能拋異常的位置
?
如何讓GC發(fā)生時(shí),讓所有線程(這里不包括執(zhí)行JNI調(diào)用的線程)都跑到最近的安全點(diǎn)上再停頓下來(lái)。HotSpot采用主動(dòng)式中斷,主動(dòng)式中斷的是當(dāng)GC需要中斷線程的時(shí)候,不直接對(duì)線程操作,僅僅簡(jiǎn)單地設(shè)置一個(gè)標(biāo)志,各個(gè)線程執(zhí)行時(shí)主動(dòng)去輪詢這個(gè)標(biāo)志,發(fā)現(xiàn)中斷標(biāo)志為真時(shí)就自己中斷掛起。
對(duì)Java線程中的JNI方法,它們既不是由JVM里的解釋器執(zhí)行的,也不是由JVM的JIT編譯器生成的,所以會(huì)缺少OopMap信息,HotSpot的解決方法是:所有經(jīng)過(guò)JNI調(diào)用邊界(調(diào)用JNI方法傳入的參數(shù)、從JNI方法傳回的返回值)的引用都必須用“句柄”(handle)包裝起來(lái)。JNI需要調(diào)用Java API的時(shí)候也必須自己用句柄包裝指針。在這種實(shí)現(xiàn)中,JNI方法里寫(xiě)的“jobject”實(shí)際上不是直接指向?qū)ο蟮闹羔?#xff0c;而是先指向一個(gè)句柄,通過(guò)句柄才能間接訪問(wèn)到對(duì)象。這樣在掃描到JNI方法的時(shí)候就不需要掃描它的棧幀了,只要掃描句柄表就可以得到所有從JNI方法能訪問(wèn)到的GC堆里的對(duì)象。
?
內(nèi)存回收如何進(jìn)行是由虛擬機(jī)所采用的GC收集器所決定的,而通常虛擬機(jī)中往往不止有一種GC收集器,目前HotSpot里面就包含有常用的garbage collectors:
·????????serial collector?:針對(duì)younggeneration的串行垃圾收集器,使用stop-the-world(就是暫停整個(gè)應(yīng)用程序的執(zhí)行)的形式,利用單線程通過(guò)復(fù)制live objects到survivor space或tenured generation的方法來(lái)進(jìn)行垃圾收集。
·????????parallel scavenge?collector?:針對(duì)younggeneration的并行垃圾收集器,利用多個(gè)GC線程來(lái)進(jìn)行垃圾收集,每個(gè)線程的GC方法和serial collector一樣。
·????????parallel ?new collector?:針對(duì)younggeneration的增強(qiáng)的并行垃圾收集器,以便可以和CMS一起使用。
·????????serial old collector?:針對(duì)tenuredgeneration的串行垃圾收集器,使用stop-the-world形式,利用單線程通過(guò)mark-sweep-compact的方法進(jìn)行垃圾收集。
·????????parallel old collector?:針對(duì)tenuredgeneration的并行垃圾收集器,利用多線程進(jìn)行垃圾收集,方法和serial old collector一樣。
·????????parallel compacting collector?:對(duì)于younggeneration使用和parallel new collector一樣的算法,對(duì)于tenured generation使用了新的算法(mark-summary-compact),該收集器用來(lái)替代parallel new collector和parallel oldcollector。
·????????concurrent mark-sweep collector?:對(duì)于younggeneration使用和parallel new collector一樣的算法,對(duì)于tenured generation使用跟應(yīng)用程序并發(fā)的方式,收集期間也有引起stop-the-world的暫停Mark階段,也有伴隨著應(yīng)用程序運(yùn)行的并發(fā) Mark和并發(fā)Sweep階段,降低了應(yīng)用程序暫停的時(shí)間。
可以看出這些垃圾收集器分為3種類(lèi)型:串行,并行,并發(fā);
總結(jié)
以上是生活随笔為你收集整理的JVM调优系列:(四)GC垃圾回收的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JVM调优系列:(三)类加载和执行机制
- 下一篇: 并发工具类(一)等待多线程完成的Coun