1、cms垃圾回收演算法在gc過程中哪個階段會暫停應用線程
Phase 1: Initial Mark(初始化標記)和 Phase 5: Final Remark(重新標記)這兩個階段會發生stop-the-world,暫停所有應用線程。
2、為什麼CMS GC時出現Concurrent Mode Failure
出現Concurrent ModeFailure現象時,解決辦法就是要讓年老代留有足夠的空間,以保證新對象空間的分配。另外在JVM BUG中有提到,JDK1.5_09版本之前,JVM參數-XX:是無效的,我這里應用環境的版本是JDK1.5_08,從gc日誌來看是可以生效的。
GC時還有一個常見的錯誤PromotionFailed,解決辦法類似,也是調整年輕代和年老代的比例,還有CMSGC的時機。
3、cms gc為什麼要stop the world
無論你選擇什麼gc,停頓都是不可避免的。CMS在初始標記和重復標記階段會停頓,最新的G1在初始標記階段也會停頓。並發gc只是把一(大)部分工作並發處理了,還是會停頓,只是時間短很多。
另外,也並不一定停頓時間短就是最好的,並發gc會跟用戶線程搶cpu。
因為full gc耗時遠高於minor gc,可能你那個說法忽略了minor gc幾十毫秒的停頓吧
4、cms垃圾回收演算法在gc過程中哪幾個階段會暫停應用縣城
中間調整過幾次,先搞了幾台機器做了驗證,後來逐步推廣的。
1、調大heap區,由原來的4g,調整到5g,young區的大小不變,還是2g,這時候old區就由2g變為3g了(這樣保證old區有足夠的空間);
2、設置-XX:UseCMSInitiatingOccupancyOnly,其實這個不關這個問題,只是發現半夜CMS進行的有點頻繁,就禁止掉了悲觀策略;
3、設置CMS區回收的比例,從80%調整到75%,讓old區盡早的進行,有足夠的空間剩餘;
為什麼要有GC(垃圾回收)?
JVM通過GC來回收堆和方法區中的內存,GC的基本原理就是找到程序中不再被使用的對象,然後回收掉這些對象佔用的內存。
主要的收集器有哪些?
引用計數器和跟蹤計數器兩種。
引用計數器記錄對象是否被引用,當計數器為零時,說明對象已經不再被使用,可以進行回收。java中的對象有復雜的引用關系,不是很適合引用計數器,所以sun jdk中並沒有實現這種GC方式。
跟蹤收集器,全局記錄數據的引用狀態,基於一定的條件觸發。執行的時候,從根集合開始掃描對象的引用關系,主要有復制(copying)、標記-清除(Mark-Sweep)、標記-壓縮(Mark-Compact)那種演算法。
5、cms垃圾回收演算法在gc過程中哪幾個階段會暫停
P
6、為什麼CMS GC時出現Concurrent Mode Failure
並發收集器(concurrentcollector)指的是回收年老代和持久代時,採用多個線程和應用線程並發執行,減少應用停頓時間,但如果參數設置不當,容易出現Concurrent ModeFailure現象,此時JVM將採用停頓的方式進行full gc,整個gc時間相當可觀,完全違背了採用CMS GC的初衷。
出現此現象的原因主要有兩個:一個是在年老代被用完之前不能完成對無引用對象的回收;一個是當新空間分配請求在年老代的剩餘空間中得到滿足。原文如(if theconcurrent collector is unable to finish reclaiming the unreachable objectsbefore the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped)。
出現此現象的具體gc日誌如下:
90003.167: [GC 90003.167: [ParNew: 261760K->0K(261952K), 0.0204310secs] 778897K->520196K(1310528K), 0.0207190 secs]
90006.049: [GC 90006.050: [ParNew: 261760K->0K(261952K), 0.0136380 secs]781956K->521446K(1310528K), 0.0138720 secs]
90010.628: [GC 90010.628: [ParNew: 261760K->261760K(261952K), 0.0000350secs]90010.628: [CMS (concurrent mode failure)[Unloadingclass sun.reflect.]
[Unloading class sun.reflect.]
[Unloading class sun.reflect.]
[Unloading class sun.reflect.]
[Unloading class sun.reflect.]
[Unloading class sun.reflect.]
[Unloading class sun.reflect.]
: 521446K->415777K(1048576K), 2.2550270 secs] 783206K->415777K(1310528K),2.2553820 secs]
90015.099: [GC 90015.099: [ParNew: 261760K->0K(261952K), 0.0198180 secs]677537K->418003K(1310528K), 0.0200650 secs]
90018.670: [GC 90018.670: [ParNew: 261760K->0K(261952K), 0.0131610 secs]679763K->419115K(1310528K), 0.0133750 secs]
90022.254: [GC 90022.254: [ParNew: 261760K->0K(261952K), 0.0151240 secs]680875K->420505K(1310528K), 0.0154180 secs]
當時的JVM參數如下:
-server -Xms1280m -Xmx1280m -Xmn256m -Xss256k -XX:PermSize=128m-XX:MaxPermSize=128m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC-XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSClassUnloadingEnabled-XX:+DisableExplicitGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
因為配置了+CMSClassUnloadingEnabled參數,所以出現Unloading classsun.reflect.的日誌,這是個好習慣,如果空間不夠時可以卸載類來釋放空間,以進行FULL GC,相反,如果gc日誌中出現了此日誌,應該檢查各代的大小設置是否合理。這里應用從啟動到上述現象出現時還沒有進行過CMS GC,出現concurrent modefailure現象的原因是年輕代GC(ParNew),年老代所剩下的空間不足以滿足年輕代,也就是開頭提到的原因二。要避免此現象,方法一是降低觸發CMS的閥值,即參數-XX:的值,默認值是68,所以這里調低到50,讓CMS GC盡早執行,以保證有足夠的空間,如下:
-server -Xms1280m -Xmx1280m -Xmn256m -Xss256k -XX:PermSize=128m-XX:MaxPermSize=128m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC-XX:CMSFullGCsBeforeCompaction=1 -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:=50-XX:+CMSClassUnloadingEnabled -XX:+DisableExplicitGC -verbose:gc-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
調完之後發現還是一樣的現象(這里有點不是很明白,年老代空間為1024m(1280m-256m),50%時觸發CMS GC,也就是在年老代512m的時候,剩下的堆空間有512m,就算年輕代全部裝進去應該也是夠的),所以懷疑是年輕代太大,有大的對象,在年老代有碎片的情況下將很難分配,所以有了第二個解決辦法,即減少年輕代大小,避免放入年老代時需要分配大的空間,同時調整full gc時壓縮碎片的頻次,減少持久代大小,以及將觸發CMS GC的閥值適當增大(因為年輕代小了,這個調大點沒關系,後面可以再調試這個參數),參數如下:
-server -Xms1280m -Xmx1280m -Xmn128m -Xss256k -XX:PermSize=96m -XX:MaxPermSize=96m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSFullGCsBeforeCompaction=1 -XX:+UseCMSCompactAtFullCollection -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:=70 -XX:+CMSClassUnloadingEnabled -XX:+DisableExplicitGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
調整完後沒有那個現象了,這里主要起作用的就是調小年輕代大小。在年老代到達826m(觸發CMS閥值(1280-128)*0.7=806m)時出現了CMS GC,用時27ms,日誌如下:
705.444: [GC 705.445: [ParNew: 130944K->0K(131008K), 0.0197680 secs]954628K->826284K(1310656K), 0.0199720 secs]
705.467:[GC [1 CMS-initial-mark: 826284K(1179648K)] 826744K(1310656K), 0.0271540 secs]
705.494:[CMS-concurrent-mark-start]
706.717:[CMS-concurrent-mark: 1.223/1.223 secs]
706.717:[CMS-concurrent-preclean-start]
706.717:[CMS-concurrent-preclean: 0.000/0.000 secs]
706.742:[CMS-concurrent-abortable-preclean-start]
706.742:[CMS-concurrent-abortable-preclean: 0.000/0.000 secs]
707.067: [GC 707.067: [ParNew: 130944K->0K(131008K), 0.0160200 secs]957228K->827348K(1310656K), 0.0162110 secs]
707.796: [GC[YG occupancy: 66671 K (131008 K)]707.796: [Rescan (parallel) ,0.0278280 secs]707.824: [weak refs processing, 0.0420770 secs] [1 CMS-remark:827348K(1179648K)] 894019K(1310656K), 0.0711970 secs]
707.877: [CMS-concurrent-sweep-start]
708.453: [GC 708.454: [ParNew: 130944K->0K(131008K), 0.0203760 secs]848439K->718796K(1310656K), 0.0205780 secs]
709.833: [GC 709.833: [ParNew: 130944K->0K(131008K), 0.0160170 secs]430484K->301411K(1310656K), 0.0161840 secs]
709.916: [CMS-concurrent-sweep: 1.974/2.040 secs]
709.916: [CMS-concurrent-reset-start]
709.951: [CMS-concurrent-reset: 0.034/0.034 secs]
711.187: [GC 711.187: [ParNew: 130944K->0K(131008K), 0.0130890 secs]413136K->283326K(1310656K), 0.0132600 secs]
觀察一段時間的gc情況,gc效率也很高,單次YGCT<20ms,FGCT <40ms:
$ jstat -gcutil 31935 1000
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 0.00 64.29 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.33 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.41 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.45 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.49 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.58 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.63 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.69 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.72 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.75 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.79 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.84 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.90 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.95 36.47 73.15 1293 19.514 6 0.211 19.725
0.00 0.00 64.99 36.47 73.15 1293 19.514 6 0.211 19.725
這時,想再測試下-XX:的值,調到80時又出現了上述現象Concurrent ModeFailure,啟動後還沒進行過CMS GC,在年老代914m時就出現了:
759.994: [GC 759.994: [ParNew: 130944K->0K(131008K), 0.0172910 secs]1040896K->911480K(1310656K), 0.0174730 secs]
760.879: [GC 760.879: [ParNew: 130944K->0K(131008K), 0.0300920 secs]1042424K->914190K(1310656K), 0.0302950 secs]
761.768: [GC 761.769: [ParNew: 130944K->130944K(131008K), 0.0000340secs]761.769: [CMS (concurrent mode failure)[Unloading classsun.reflect.GeneratedMethodAccessor342]edMethodAccessor348]
[Unloading class sun.reflect.GeneratedMethodAccessor411]
[Unloading class sun.reflect.GeneratedMethodAccessor407]
[Unloading class sun.reflect.GeneratedMethodAccessor541]
最後總結下,出現Concurrent ModeFailure現象時,解決辦法就是要讓年老代留有足夠的空間,以保證新對象空間的分配。另外在JVM BUG中有提到,JDK1.5_09版本之前,JVM參數-XX:是無效的,我這里應用環境的版本是JDK1.5_08,從gc日誌來看是可以生效的。
GC時還有一個常見的錯誤PromotionFailed,解決辦法類似,也是調整年輕代和年老代的比例,還有CMSGC的時機。
7、cms gc過程中哪幾個階段暫停應用程序
問題解決:中間調整過幾次,先搞了幾台機器做了驗證,後來逐步推廣的。
1、調大heap區,由原來的4g,調整到5g,young區的大小不變,還是2g,這時候old區就由2g變為3g了(這樣保證old區有足夠的空間);
2、設置-XX:UseCMSInitiatingOccupancyOnly,其實這個不關這個問題,只是發現半夜CMS進行的有點頻繁,就禁止掉了悲觀策略;
3、設置CMS區回收的比例,從80%調整到75%,讓old區盡早的進行,有足夠的空間剩餘;
為什麼要有GC(垃圾回收)?
JVM通過GC來回收堆和方法區中的內存,GC的基本原理就是找到程序中不再被使用的對象,然後回收掉這些對象佔用的內存。
8、CMS GC會不會回收Direct ByteBuffer的內存
Oracle JDK 6u32前的版本不會。
Direct ByteBuffer是在Java Heap外分配內存,NIO等東西里使用的比較多,但Direct ByteBuffer分配出去的內存其實也是由GC負責回收的,而不像之前一篇文章里的Unsafe是完全自行管理的,Hotspot在GC時會掃描Direct ByteBuffer對象是否有引用,如沒有則同時也會回收其佔用的堆外內存,但不幸的是在6u32前的版本里,CMS GC有bug會導致可能回收不掉,具體的bug id為 7112034 ,在鏈接的Backport信息里,可以看到這個bug是在hotspot 20.7的版本里修復的(hotspot的版本號通過java -version的最後一行Java Hotspot Version之類的可以看到),6u32帶的就是這個版本,所以6u32是會回收的。
回收不掉的情況下會造成的問題是明明已經不用了,但堆外內存仍然被消耗掉,悲慘的情況下可能會導致堆外內存耗光。
Direct ByteBuffer除了上面這個bug可能造成堆外內存耗光外,還有一種場景也可能會造成堆外內存耗光,如Direct ByteBuffer對象晉升到了Old區,那這個時候就只能等Full GC觸發(CMS GC的情況下等CMS GC),因此在Direct ByteBuffer使用較多,存活時間較長的情況下,有可能會導致堆外內存耗光(因為Direct ByteBuffer本身對象所佔用的空間是很小的)。
對於上面這種類型的應用,最好是在啟動參數上增加-XX:MaxDirectMemorySize=x[m|g],例如-XX:MaxDirectMemorySize=500m
這個參數默認的大小是-Xmx的值(在沒設置MaxDirectMemorySize參數的情況下,用jinfo -flag等方式會看到默認值是-1,但VM.maxDirectMemory這個方法里發現是-1,則會以-Xmx作為默認值),此參數的含義是當Direct ByteBuffer分配的堆外內存到達指定大小後,即觸發Full GC(這段邏輯請見Bits.reserveMemory的代碼),如Full GC後仍然分配不出Direct ByteBuffer需要的空間,則會報OOM錯誤:
java.lang.OutOfMemoryError: Direct buffer memory
因為上面所說的狀況,如碰到堆外內存佔用較多的場景,可以嘗試強制執行Full GC(強制的方法為執行jmap -histo:live)看看,多執行一兩次,如堆外內存下降的話,很有可能就是Direct ByteBuffer造成的,對於這種情況,通常加上上面的啟動參數就可解決。