在了解了上一章中GC算法的基本概念之后,本章將深入到各GC算法的具體實現(xiàn)中。對大多數(shù)JVM來說,一般需要選擇兩種GC算法,一種用于回收新生代內(nèi)存區(qū),另一種用于回收老年代內(nèi)存區(qū)域。
新生代和老年代GC算法的可能組合如下表所示,如果不指定的話,將會在新生代和老年代中選擇默認的GC算法。下表中的GC算法組合是基于java 8的,在其他Java版本中可能會有所不同。
| 新生代GC算法 | 老年代GC算法 | JVM參數(shù) |
|---|---|---|
| Incremental | Incremental | -Xincgc |
| Serial | Serial | -XX:+UseSerialGC |
| Parallel Scavenge | Serial | -XX:+UseParallelGC -XX:+UseParallelOldGC |
| Parallel New | Serial | N/A |
| Serial | Parallel Old | N/A |
| Parallel Scavenge | Parallel Old | -XX:+UseParallelGC -XX:+UseParallelOldGC |
| Parallel New | Parallel Old | N/A |
| Serial | CMS | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC |
| Parallel Scavenge | CMS | N/A |
| Parallel New | CMS | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC |
| G1 | -XX:+UseG1GC |
如果現(xiàn)在覺得上表看起來覺得很復(fù)雜,請別著急。一般常用的是上面加粗的四種組合。剩下的組合一般是已經(jīng)不用了,或者是不再支持,或者在實際中基本不使用。所以,在接下來的文章中,只介紹上面這四種組合。
新生代和老年代的串行GC(Serial GC)新生代和老年代的并行GC(Parallel GC)新生代并行GC(Parallel GC) + 老年代CMS部分新生代老年代的G1串行GC對于新生代使用標記復(fù)制(mark-copy)策略,對老年代使用標記清除整理(mark-sweep-compact)策略進行垃圾回收。這些收集器是單線程的,不能并發(fā)的對垃圾進行回收。并且在垃圾回收動作時會暫停整個應(yīng)用線程(stop-the-world)。
這種GC算法無法充分利用硬件資源,即使有多個核,在GC時也只用其中一個。在新生代和老年代啟動串行GC的命令如下:
java -XX:+UseSerialGC com.mypackages.MyExecutableClass這種GC算法一般并不常用,只有在堆內(nèi)存為幾百MB,并且應(yīng)用運行在單核CPU上時才使用。一般應(yīng)用都部署在多核的服務(wù)器上,如果使用串行GC會在GC時無法充分利用資源,造成性能瓶頸,提高應(yīng)用延遲和降低吞吐量。
接下來我們看一個串行GC的垃圾收集日志信息,使用如下命令使應(yīng)用打印出GC日志,
-XX:+PRingGCDetails -XX:+PringGCDateStamps -XX:+PringGCTimeStamps輸出日志如下,
2015-05-26T14:45:37.987-0200: 151.126: [GC (Allocation Failure) 151.126: [DefNew:629119K->69888K(629120K), 0.0584157 secs] 1619346K->1273247K(2027264K), 0.0585007 secs][Times: user=0.06 sys=0.00, real=0.06 secs]2015-05-26T14:45:59.690-0200: 172.829: [GC (Allocation Failure) 172.829: [DefNew:629120K->629120K(629120K), 0.0000372 secs]172.829: [Tenured: 1203359K->755802K(1398144K), 0.1855567 secs] 1832479K->755802K(2027264K), [Metaspace:6741K->6741K(1056768K)], 0.1856954 secs] [Times: user=0.18 sys=0.00, real=0.18 secs]從上面這段日志信息中可以看到進行了兩次GC,第一次清理了新生代,第二次清理了新生代和老年代空間。
清理新生代內(nèi)存的GC事件日志如下,
2015-05-26T14:45:37.987-0200
對照上面的不同字段進行說明, (1)2015-05-26T14:45:37.987-0200,發(fā)生本次GC動作的時間 (2)151.126,GC事件發(fā)生時距離該JVM啟動的時間,單位為秒 (3)GC,用于區(qū)分是Minor GC還是Full GC。這里表示本次是Minor GC (4)Allocation Failure,導(dǎo)致本次進行GC的原因。在這里,本次GC是由于無法為新的數(shù)據(jù)結(jié)構(gòu)在新生代中分配內(nèi)存空間導(dǎo)致的。 (5)DefNew,垃圾收集器的名稱。這個名稱表示的是在新生代中進行的單線程,標記-復(fù)制,全應(yīng)用暫停的垃圾收集器 (6)629119K->69888K,表示新生代內(nèi)存空間在GC前后的大小。 (7)629120K,表示新生代的總大小 (8)1619346K->1273247K,堆內(nèi)存在GC前后的大小 (9)2027264K,堆內(nèi)存中可用大小 (10)0.0585007 secs,GC動作的時間,單位為秒 (11)Times: user=0.06 sys=0.00, real=0.06 secs,GC動作的時間,其中
user- 表示在此次垃圾回收過程中,所有GC線程消耗的CPU時間之和sys - 表示GC過程中操作系統(tǒng)調(diào)用和系統(tǒng)等等事件所消耗的時間real - 應(yīng)用暫停的總時間。由于串行GC是單線程的,所以暫停總時間等于user時間和sys時間之和 經(jīng)過上面這些分析后,我們可以更加清楚的從GC日志中獲取到當(dāng)時的詳細信息。在GC前,總共使用了1619346K堆內(nèi)存,其中新生代使用了629119K。通過計算就可以得到老年代使用了990227K。
GC后,新生代釋放出了559231K內(nèi)存空間,但是堆的總內(nèi)存僅僅釋放了346099K。也就是說,在本次GC時,有213132K的對象從新生代升級到了老年代區(qū)域。
下圖形象的表明了本次GC前后內(nèi)存的變化情況。
理解了Minor GC事件后,接下來我們看一下第二次GC的日志,
2015-05-26T14:45:59.690-0200
對上面各組數(shù)據(jù)進行分析, (1)2015-05-26T14:45:59.690-0200,本次GC事件發(fā)生的時間 (2)172.829,GC時JVM的啟動總時間,單位為秒。 (3)[DefNew: 629120K->629120K(629120K), 0.0000372 secs,由于分配內(nèi)存不足導(dǎo)致的一次新生代GC。在本次GC時,首先進行的是新生代的DefNew類型GC,將新生代的內(nèi)存使用從629120K降低到0。注意在這里,JVM的顯示有問題,誤認為年輕代內(nèi)存使用完了。本次GC耗時0.0000372秒 (4)Tenured,老年代垃圾收集器的名稱。Tenured表示一個單線程,暫停整個應(yīng)用線程的標記清除整理的垃圾收集過程。 (5)1203359K->755802K,老年代在垃圾回收前后的內(nèi)存使用情況 (6)1398144K,老年代總內(nèi)存數(shù) (7)0.1855567 secs,老年代垃圾回收的耗時 (8)1832479K->755802K,垃圾回收前后總堆內(nèi)存的變化情況(包括新生代和老年代) (9)2027264K,JVM堆的可用內(nèi)存 (10)[Metaspace: 6741K->6741K(1056768K)],元數(shù)據(jù)區(qū)在垃圾回收前后的內(nèi)存使用情況,從這里可以看出,本次GC時并沒有對元數(shù)據(jù)區(qū)的內(nèi)存進行回收 (11)[Times: user=0.18 sys=0.00, real=0.18 secs],GC事件的耗時,
user- 表示在此次垃圾回收過程中,所有GC線程消耗的CPU時間之和sys - 表示GC過程中操作系統(tǒng)調(diào)用和系統(tǒng)等等事件所消耗的時間real - 應(yīng)用暫停的總時間。由于串行GC是單線程的,所以暫停總時間等于user時間和sys時間之和 本次Full GC與上面的Minor GC區(qū)別十分明顯,F(xiàn)ull GC是會對老年代和元數(shù)據(jù)區(qū)進行垃圾回收的。本次垃圾回收的過程如下圖所示,
在這種GC模式下,新生代使用標記復(fù)制策略,老年代使用標記清除整理策略。新生代和老年代的GC事件都會導(dǎo)致所有應(yīng)用線程暫停。新生代和老年代在復(fù)制(copy)或整理(compact)階段都使用多線程,這也是并行GC名稱的來由。使用這種GC算法,可以降低垃圾回收的時間消耗。 在垃圾回收時的并行線程數(shù),可以由參數(shù)-XX:+ParallelGCThreads=NNN來設(shè)置。該參數(shù)的默認值是服務(wù)器的核數(shù)。 使用并行GC,可以用以下三種命令模式:
并行垃圾收集器一般用在多核服務(wù)器上,在多核服務(wù)器上使用并行GC,能重復(fù)利用硬件資源,提高應(yīng)用的吞吐量, - 在垃圾收集過程中,會利用所有的核并行進行垃圾回收動作,降低應(yīng)用暫停時間 - 在垃圾回收間歇期,垃圾收集器不工作,不會消耗系統(tǒng)資源 另一方面,并行GC的所有階段都不能被中斷,所以這些垃圾收集器仍然有可能在所有應(yīng)用線程停止時陷入長時間的暫停中。所以,如果要求系統(tǒng)低延遲,那么不建議使用這種垃圾收集器。 接下來,我們看一下并行GC時的日志信息。如下所示,
2015-05-26T14:27:40.915-0200: 116.115: [GC (Allocation Failure) [PSYoungGen: 2694440K->1305132K(2796544K)] 9556775K->8438926K(11185152K), 0.2406675 secs] [Times: user=1.77sys=0.01, real=0.24 secs]2015-05-26T14:27:41.155-0200: 116.356: [Full GC (Ergonomics) [PSYoungGen: 1305132K->0K(2796544K)] [ParOldGen: 7133794K->6597672K(8388608K)] 8438926K->6597672K(11185152K),[Metaspace: 6745K->6745K(1056768K)], 0.9158801 secs] [Times: user=4.49 sys=0.64,real=0.92 secs]接下來詳細分析Minor GC時的日志信息。
2015-05-26T14:27:40.915-0200
(1)2015-05-26T14:27:40.915-0200,本次GC事件發(fā)生的時間 (2)116.115,GC時JVM的啟動總時間,單位為秒。 (3)GC,用于區(qū)分Minor GC和Full GC。這里表示本次為Minor GC (4)Allocation Failure,導(dǎo)致本次GC的原因。是由于新生代中無法為新對象分配內(nèi)存 (5)PSYoungGen,垃圾收集器的名稱,這里表示這是一個并行標記復(fù)制,暫停全部應(yīng)用的新生代垃圾收集器 (6)2694440K->1305132K,GC前后新生代的內(nèi)存空間使用量 (7)2796544K,新生代總內(nèi)存量 (8)9556775K->8438926K,垃圾回收前后總堆內(nèi)存的變化情況(包括新生代和老年代) (9)11185152K,JVM堆的可用內(nèi)存 (10)0.2406675 secs,GC事件的耗時 (11)[Times: user=1.77 sys=0.01, real=0.24 secs],GC事件的耗時,
user- 表示在此次垃圾回收過程中,所有GC線程消耗的CPU時間之和sys - 表示GC過程中操作系統(tǒng)調(diào)用和系統(tǒng)等等事件所消耗的時間real - 應(yīng)用暫停的總時間。在并行GC中,這個數(shù)值應(yīng)該接近于(user + sys) / GC線程數(shù)。即單個核上平均的暫停時間,在這里線程數(shù)為8。由于某些過程是不能并行執(zhí)行的,所以這個值會比剛才求的均值略高。
總結(jié)一下本次GC過程就是,在GC前整個堆內(nèi)存使用了9556775K,其中新生代使用了2694440K,那么老年代使用了6862335K。新生代的GC導(dǎo)致新生代釋放了1389308K的空間,但是堆的總空間只釋放了1117849K。這意味著有271459K的對象從新生代升級到了老年代中,整個過程如下圖所示, 
在理解了新生代的并行GC過程后,我們接下來分析一些并行GC在Full GC時的表現(xiàn),
2015-05-26T14:27:41.155-0200
(1)2015-05-26T14:27:41.155-0200,本次GC事件發(fā)生的時間 (2)116.356,GC時JVM的啟動總時間,單位為秒。 (3)Full GC,表示本次是一次Full GC,將會對新生代和老年代的內(nèi)存空間進行回收 (4)Ergonomics,本次GC的觸發(fā)原因。這里是由于JVM認為此刻是一次適合進行垃圾回收的時間 (5)[PSYoungGen: 1305132K->0K(2796544K)],垃圾收集器的名稱。PSYoungGen表示這是一次新生代中進行的標記復(fù)制,暫停全部應(yīng)用的新生代GC。新生代的內(nèi)存空間使用量從1305132K降低到0。一般來說,進行了一次Full GC后,新生代的內(nèi)存空間將會被全部清理。 (6)ParOldGen,老年代中的垃圾收集器類型。在這里ParOldGen表示在老年代中使用的標記清除整理,暫停全部應(yīng)用的老年代垃圾收集器。 (7)133794K->6597672K,老年代垃圾回收前后的內(nèi)存使用情況 (8)8388608K,老年代的總內(nèi)存大小 (9)8438926K->6597672K,垃圾回收前后總堆內(nèi)存的變化情況(包括新生代和老年代) (10)11185152K,JVM堆的總內(nèi)存 (11)[Metaspace: 6745K->6745K(1056768K)],元數(shù)據(jù)區(qū)在垃圾回收前后的內(nèi)存使用情況,從這里可以看出,本次GC時并沒有對元數(shù)據(jù)區(qū)的內(nèi)存進行回收 (12)0.9158801 secs,本次GC的耗時,單位為秒 (13)[Times: user=4.49 sys=0.64,real=0.92 secs],GC事件的耗時,
user- 表示在此次垃圾回收過程中,所有GC線程消耗的CPU時間之和sys - 表示GC過程中操作系統(tǒng)調(diào)用和系統(tǒng)等等事件所消耗的時間real - 應(yīng)用暫停的總時間。在并行GC中,這個數(shù)值應(yīng)該接近于(user + sys) / GC線程數(shù)。即單個核上平均的暫停時間,在這里線程數(shù)為8。由于某些過程是不能并行執(zhí)行的,所以這個值會比剛才求的均值略高。 并行GC過程中的Full GC也與Minor GC有些不同。Full GC不僅會對新生代進行垃圾回收,也會清理老年代和元數(shù)據(jù)區(qū)。在Full GC前后,JVM各區(qū)內(nèi)存變化情況如下圖所示, 
新聞熱點
疑難解答