前言
創(chuàng)新互聯(lián)專業(yè)網(wǎng)站建設(shè)、做網(wǎng)站,集網(wǎng)站策劃、網(wǎng)站設(shè)計、網(wǎng)站制作于一體,網(wǎng)站seo、網(wǎng)站優(yōu)化、網(wǎng)站營銷、軟文發(fā)稿等專業(yè)人才根據(jù)搜索規(guī)律編程設(shè)計,讓網(wǎng)站在運(yùn)行后,在搜索中有好的表現(xiàn),專業(yè)設(shè)計制作為您帶來效益的網(wǎng)站!讓網(wǎng)站建設(shè)為您創(chuàng)造效益。
文章要求讀者熟悉 JVM 內(nèi)置的通用垃圾回收原則。堆內(nèi)存劃分為 Eden、Survivor 和 Tenured/Old 空間,代假設(shè)和其他不同的 GC 算法超出了本文討論的范圍。
Minor GC
從年輕代空間(包括 Eden 和 Survivor 區(qū)域)回收內(nèi)存被稱為 Minor GC。這一定義既清晰又易于理解。但是,當(dāng)發(fā)生Minor GC事件的時候,有一些有趣的地方需要注意到:
1、當(dāng) JVM 無法為一個新的對象分配空間時會觸發(fā) Minor GC,比如當(dāng) Eden 區(qū)滿了。所以分配率越高,越頻繁執(zhí)行 Minor GC。
2、內(nèi)存池被填滿的時候,其中的內(nèi)容全部會被復(fù)制,指針會從0開始跟蹤空閑內(nèi)存。Eden 和 Survivor 區(qū)進(jìn)行了標(biāo)記和復(fù)制操作,取代了經(jīng)典的標(biāo)記、掃描、壓縮、清理操作。所以 Eden 和 Survivor 區(qū)不存在內(nèi)存碎片。寫指針總是停留在所使用內(nèi)存池的頂部。
3、執(zhí)行 Minor GC 操作時,不會影響到永久代。從永久代到年輕代的引用被當(dāng)成 GC roots,從年輕代到永久代的引用在標(biāo)記階段被直接忽略掉。
4、質(zhì)疑常規(guī)的認(rèn)知,所有的 Minor GC 都會觸發(fā)“全世界的暫停(stop-the-world)”,停止應(yīng)用程序的線程。對于大部分應(yīng)用程序,停頓導(dǎo)致的延遲都是可以忽略不計的。其中的真相就 是,大部分 Eden 區(qū)中的對象都能被認(rèn)為是垃圾,永遠(yuǎn)也不會被復(fù)制到 Survivor 區(qū)或者老年代空間。如果正好相反,Eden 區(qū)大部分新生對象不符合 GC 條件,Minor GC 執(zhí)行時暫停的時間將會長很多。
所以 Minor GC 的情況就相當(dāng)清楚了——每次 Minor GC 會清理年輕代的內(nèi)存。
Major GC vs Full GC
大家應(yīng)該注意到,目前,這些術(shù)語無論是在 JVM 規(guī)范還是在垃圾收集研究論文中都沒有正式的定義。但是我們一看就知道這些在我們已經(jīng)知道的基礎(chǔ)之上做出的定義是正確的,Minor GC 清理年輕帶內(nèi)存應(yīng)該被設(shè)計得簡單:
Major GC 是清理老年代。
Full GC 是清理整個堆空間—包括年輕代和老年代。
很不幸,實(shí)際上它還有點(diǎn)復(fù)雜且令人困惑。首先,許多 Major GC 是由 Minor GC 觸發(fā)的,所以很多情況下將這兩種 GC 分離是不太可能的。另一方面,許多現(xiàn)代垃圾收集機(jī)制會清理部分永久代空間,所以使用“cleaning”一詞只是部分正確。
這使得我們不用去關(guān)心到底是叫 Major GC 還是 Full GC,大家應(yīng)該關(guān)注當(dāng)前的 GC 是否停止了所有應(yīng)用程序的線程,還是能夠并發(fā)的處理而不用停掉應(yīng)用程序的線程。
這種混亂甚至內(nèi)置到 JVM 標(biāo)準(zhǔn)工具。下面一個例子很好的解釋了我的意思。讓我們比較兩個不同的工具 Concurrent Mark 和 Sweep collector (-XX:+UseConcMarkSweepGC)在 JVM 中運(yùn)行時輸出的跟蹤記錄。
第一次嘗試通過 jstat 輸出:
my-precious:?me$?jstat?-gc?-t?4235?1sTime?S0C????S1C????S0U????S1U??????EC???????EU????????OC?????????OU???????MC?????MU????CCSC???CCSU???YGC?????YGCT????FGC????FGCT?????GCT????5.7?34048.0?34048.0??0.0???34048.0?272640.0?194699.7?1756416.0???181419.9??18304.0?17865.1?2688.0?2497.6??????3????0.275???0??????0.000????0.275?6.7?34048.0?34048.0?34048.0??0.0???272640.0?247555.4?1756416.0???263447.9??18816.0?18123.3?2688.0?2523.1??????4????0.359???0??????0.000????0.359?7.7?34048.0?34048.0??0.0???34048.0?272640.0?257729.3?1756416.0???345109.8??19072.0?18396.6?2688.0?2550.3??????5????0.451???0??????0.000????0.451?8.7?34048.0?34048.0?34048.0?34048.0?272640.0?272640.0?1756416.0??444982.5??19456.0?18681.3?2816.0?2575.8??????7????0.550???0??????0.000????0.550?9.7?34048.0?34048.0?34046.7??0.0???272640.0?16777.0??1756416.0???587906.3??20096.0?19235.1?2944.0?2631.8??????8????0.720???0??????0.000????0.72010.7?34048.0?34048.0??0.0???34046.2?272640.0?80171.6??1756416.0???664913.4??20352.0?19495.9?2944.0?2657.4??????9????0.810???0??????0.000????0.81011.7?34048.0?34048.0?34048.0??0.0???272640.0?129480.8?1756416.0???745100.2??20608.0?19704.5?2944.0?2678.4?????10????0.896???0??????0.000????0.89612.7?34048.0?34048.0??0.0???34046.6?272640.0?164070.7?1756416.0???822073.7??20992.0?19937.1?3072.0?2702.8?????11????0.978???0??????0.000????0.97813.7?34048.0?34048.0?34048.0??0.0???272640.0?211949.9?1756416.0???897364.4??21248.0?20179.6?3072.0?2728.1?????12????1.087???1??????0.004????1.09114.7?34048.0?34048.0??0.0???34047.1?272640.0?245801.5?1756416.0???597362.6??21504.0?20390.6?3072.0?2750.3?????13????1.183???2??????0.050????1.23315.7?34048.0?34048.0??0.0???34048.0?272640.0?21474.1??1756416.0???757347.0??22012.0?20792.0?3200.0?2791.0?????15????1.336???2??????0.050????1.38616.7?34048.0?34048.0?34047.0??0.0???272640.0?48378.0??1756416.0???838594.4??22268.0?21003.5?3200.0?2813.2?????16????1.433???2??????0.050????1.484
這個片段是 JVM 啟動后第17秒提取的。基于該信息,我們可以得出這樣的結(jié)果,運(yùn)行了12次 Minor GC、2次 Full GC,時間總跨度為50毫秒。通過 jconsole 或者 jvisualvm 這樣的基于GUI的工具你能得到同樣的結(jié)果。
java?-XX:+PrintGCDetails?-XX:+UseConcMarkSweepGC?eu.plumbr.demo.GarbageProducer3.157:?[GC?(Allocation?Failure)?3.157:?[ParNew:?272640K->34048K(306688K),?0.0844702?secs]?272640K->69574K(2063104K),?0.0845560?secs]?[Times:?user=0.23?sys=0.03,?real=0.09?secs]?4.092:?[GC?(Allocation?Failure)?4.092:?[ParNew:?306688K->34048K(306688K),?0.1013723?secs]?342214K->136584K(2063104K),?0.1014307?secs]?[Times:?user=0.25?sys=0.05,?real=0.10?secs]?...?cut?for?brevity?...11.292:?[GC?(Allocation?Failure)?11.292:?[ParNew:?306686K->34048K(306688K),?0.0857219?secs]?971599K->779148K(2063104K),?0.0857875?secs]?[Times:?user=0.26?sys=0.04,?real=0.09?secs]?12.140:?[GC?(Allocation?Failure)?12.140:?[ParNew:?306688K->34046K(306688K),?0.0821774?secs]?1051788K->856120K(2063104K),?0.0822400?secs]?[Times:?user=0.25?sys=0.03,?real=0.08?secs]?12.989:?[GC?(Allocation?Failure)?12.989:?[ParNew:?306686K->34048K(306688K),?0.1086667?secs]?1128760K->931412K(2063104K),?0.1087416?secs]?[Times:?user=0.24?sys=0.04,?real=0.11?secs]?13.098:?[GC?(CMS?Initial?Mark)?[1?CMS-initial-mark:?897364K(1756416K)]?936667K(2063104K),?0.0041705?secs]?[Times:?user=0.02?sys=0.00,?real=0.00?secs]?13.102:?[CMS-concurrent-mark-start]13.341:?[CMS-concurrent-mark:?0.238/0.238?secs]?[Times:?user=0.36?sys=0.01,?real=0.24?secs]?13.341:?[CMS-concurrent-preclean-start]13.350:?[CMS-concurrent-preclean:?0.009/0.009?secs]?[Times:?user=0.03?sys=0.00,?real=0.01?secs]?13.350:?[CMS-concurrent-abortable-preclean-start]13.878:?[GC?(Allocation?Failure)?13.878:?[ParNew:?306688K->34047K(306688K),?0.0960456?secs]?1204052K->1010638K(2063104K),?0.0961542?secs]?[Times:?user=0.29?sys=0.04,?real=0.09?secs]?14.366:?[CMS-concurrent-abortable-preclean:?0.917/1.016?secs]?[Times:?user=2.22?sys=0.07,?real=1.01?secs]?14.366:?[GC?(CMS?Final?Remark)?[YG?occupancy:?182593?K?(306688?K)]14.366:?[Rescan?(parallel)?,?0.0291598?secs]14.395:?[weak?refs?processing,?0.0000232?secs]14.395:?[class?unloading,?0.0117661?secs]14.407:?[scrub?symbol?table,?0.0015323?secs]14.409:?[scrub?string?table,?0.0003221?secs][1?CMS-remark:?976591K(1756416K)]?1159184K(2063104K),?0.0462010?secs]?[Times:?user=0.14?sys=0.00,?real=0.05?secs]?14.412:?[CMS-concurrent-sweep-start]14.633:?[CMS-concurrent-sweep:?0.221/0.221?secs]?[Times:?user=0.37?sys=0.00,?real=0.22?secs]?14.633:?[CMS-concurrent-reset-start]14.636:?[CMS-concurrent-reset:?0.002/0.002?secs]?[Times:?user=0.00?sys=0.00,?real=0.00?secs]
在點(diǎn)頭同意這個結(jié)論之前,讓我們看看來自同一個 JVM 啟動收集的垃圾收集日志的輸出。顯然- XX :+ PrintGCDetails 告訴我們一個不同且更詳細(xì)的故事:
基于這些信息,我們可以看到12次 Minor GC 后開始有些和上面不一樣了。沒有運(yùn)行兩次 Full GC,這不同的地方在于單個 GC 在永久代中不同階段運(yùn)行了兩次:
1、最初的標(biāo)記階段,用了0.0041705秒也就是4ms左右。這個階段會暫?!叭澜纾?stop-the-world)”的事件,停止所有應(yīng)用程序的線程,然后開始標(biāo)記。
2、并行執(zhí)行標(biāo)記和清洗階段。這些都是和應(yīng)用程序線程并行的。
3、最后 Remark 階段,花費(fèi)了0.0462010秒約46ms。這個階段會再次暫停所有的事件。
4、并行執(zhí)行清理操作。正如其名,此階段也是并行的,不會停止其他線程。
所以,正如我們從垃圾回收日志中所看到的那樣,實(shí)際上只是執(zhí)行了 Major GC 去清理老年代空間而已,而不是執(zhí)行了兩次 Full GC。
如果你是后期做決 定的話,那么由 jstat 提供的數(shù)據(jù)會引導(dǎo)你做出正確的決策。它正確列出的兩個暫停所有事件的情況,導(dǎo)致所有線程停止了共計50ms。但是如果你試圖優(yōu)化吞吐量,你會被誤導(dǎo)的。清 單只列出了回收初始標(biāo)記和最終 Remark 階段,jstat的輸出看不到那些并發(fā)完成的工作。
結(jié)論
考慮到這種情況,最好避免以 Minor、Major、Full GC 這種方式來思考問題。而應(yīng)該監(jiān)控應(yīng)用延遲或者吞吐量,然后將 GC 事件和結(jié)果聯(lián)系起來。
歡迎大家關(guān)注我的公種浩【程序員追風(fēng)】,文章都會在里面更新,整理的資料也會放在里面。
隨著這些 GC 事件的發(fā)生,你需要額外的關(guān)注某些信息,GC 事件是強(qiáng)制所有應(yīng)用程序線程停止了還是并行的處理了部分事件。
最后
歡迎大家一起交流,喜歡文章記得關(guān)注我點(diǎn)個贊喲,感謝支持!