一、基本收集算法
1. 複製:將堆內分成兩個相同空間,從根(ThreadLocal的對象,靜態對象)開始訪問每一個關聯的活躍對象,將空間A的活躍對象全部複製到空間B,然後一次性回收整個空間A。
因爲只訪問活躍對象,將所有活動對象複製走之後就清空整個空間,不用去訪問死對象,所以遍歷空間的成本較小,但需要巨大的複製成本和較多的內存。
優點:只訪問活躍對象,遍歷快
缺點:需要內存比較多,浪費一部分內存,不適合年老代
2. 標記清除(mark-sweep):收集器先從根開始訪問所有活躍對象,標記爲活躍對象。然後再遍歷一次整個內存區域,把所有沒有標記活躍的對象進行回收處理。該算法遍歷整個空間的成本較大暫停時間隨空間大小線性增大,而且整理後堆裏的碎片很多。
優點:不浪費內存
缺點:遍歷2次,2次標記效率都不高,內存碎片多
3. 標記整理(mark-sweep-compact):綜合了上述兩者的做法和優點,先標記活躍對象,然後將其合併成較大的內存塊。
移動了活躍對象,回收後留下完整內存塊
二 JVM GC收集器
1.Serial收集器 -XX:+UseSerialGC
複製收集算法
單線程收集器,收集時會暫停所有工作線程(我們將這件事情稱之爲Stop The World,下稱STW),使用複製收集算法,虛擬機運行在Client模式時的默認新生代收集器。
2.ParNew收集器 -XX:+UseParNewGC 複製收集算法 多線程執行
ParNew收集器就是Serial的多線程版本,除了使用多條收集線程外,其餘行爲包括算法、STW、對象分配規則、回收策略等都與Serial收集器一摸一樣。對應的這種收集器是虛擬機運行在Server模式的默認新生代收集器,在單CPU的環境中,ParNew收集器並不會比Serial收集器有更好的效果。
3.Parallel Scavenge收集器 -XX:+UseParallelGC 複製算法,吞吐量最大化
Parallel Scavenge收集器(下稱PS收集器)也是一個多線程收集器,也是使用複製算法,但它的對象分配規則與回收策略都與ParNew收集器有所不同,它是以吞吐量最大化(即GC時間佔總運行時間最小)爲目標的收集器實現,它允許較長時間的STW換取總吞吐量最大化。
4.Serial Old收集器 -XX:+UseSerialGC 標記整理算法
Serial Old是單線程收集器,使用標記-整理算法,是老年代的收集器,上面三種都是使用在新生代收集器。
5.Parallel Old收集器 -XX:+UseParallelOldGC 標記整理算法多線程執行
老年代版本吞吐量優先收集器,使用多線程和標記-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的話,老年代除Serial Old外別無選擇,因爲PS無法與CMS收集器配合工作。
6.CMS(Concurrent Mark Sweep)收集器 XX:+UseconcMarkSweepGC 標記,置換 不壓縮 算法
壓縮需要加參數-XX:CMSFullGCsBeforeCompaction(多少次FullGC後壓縮內存)
或者 -XX+UseCMSCompactAtFullCollection(CMS後壓縮內存)
CMS收集器使用的是標記-清除算法,也就是說它在運行期間會產生空間碎片,所以虛擬機提供了參數開啓CMS收集結束後再進行一次內存壓縮。
CMS大體過程爲:
(1)併發gc會先暫停jvm運行,然後標誌應用中的強可達對象;init-mark,stop the world
(2)開始併發標誌引用強可達對象的對象;這時程序還在運行,concurrent-mark
(3)因爲程序正在運行,這時重新標誌一下引用被修改的對象(這時jvm會再次暫停)rescan
(4)重新標記後,堆中所有活動對象被標記出來了,然後開發開始回收堆中的垃圾對象;sweep
(5)該算法不進行compact,而是在內存中建立一個空閒內存的鏈表,下次在old區分配空間時,會先找到一個符合條件的空閒空間給對象,該方式會影響old區和young區的對象空間分配,影響young區主要是由於有些young區的對象需要轉至old區.
三、JVM設置需要注意的地方
1.-Xmn 與 -XX:NewRatio 不要同時設置
2.-Xss一定要設置,JDK5.0以後每個線程堆棧大小爲1M,對一般的應用來說,256K足夠了,如果發生StackOverFlow錯誤,那麼加大這個值也沒用。
jvm 棧 和本地方法棧可能存在的異常:
StackOverFlowError:線程請求的棧深度>虛擬機所允許的深度
OutOfMemory:當擴展時無法申請到足夠的內存(大部分虛擬機都允許動態擴展,java虛擬機規範也允許固定長度)
3.設置-XX:+DisableExplicitGC,避免RMI或應用顯示調用System.gc().如果有此類調用gc日誌中會含有System標誌。
修正:
1.jdk6 u32之前存在一個bug,某些情況下,cms GC 不回收Dirent byte buffer,堆外內存。
2.使用這個參數可能會導致cms GC觸發之前,堆外內存過大時,物理內存或地址空間不足,出現OOM錯誤。
因此建議:
1.不使用這個參數,改爲:-XX:+ExplicitGCInvokesConcurrent
2.配置堆外內存最大空間:-XX:MaxDirectMemorySize。不配置時,默認與-Xmx值相同。可能造成OOM.
4.-XX:MaxTenuringThreshold 對象最多!經過多少次minor GC後進入年老代。設置爲0,則不經過survior拷貝,直接進入年老代。增大此值會增加對象在Minor GC被回收的機率,減小此值會增加對象進入年老代的機率。注意最多,有可能不經過那麼多次,比如
4.1.大的數組對象,如果在survivor空間中相同年齡所有對象大小的累計值大於survivor空間的一半,大於或等於個年齡的對象就可以直接進入老年代,無需達到MaxTenuringThreshold中要求的年齡
4.2.配置了 -XX:PretenureSizeThreshold 參數,則大於指定byte的對象直接進入年老代分配。
5.持久代GC時回收無引用的廢棄常量與無用類。無用類需滿足以下三個條件:
5.1.該類所有的實例都已經被GC,也就是JVM中不存在該Class的任何實例。
5.2.加載該類的ClassLoader已經被GC。
5.3.該類對應的java.lang.Class 對象沒有在任何地方被引用,如不能在任何地方通過反射訪問該類的方法。
是否對類進行回收可使用-XX:+ClassUnloading參數進行控制,還可以使用-verbose:class或者-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看類加載、卸載信息。
在大量使用反射、動態代理、CGLib等bytecode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要JVM具備類卸載的支持以保證永久代不會溢出
6 -XX:CMSInitiatingOccupancyFraction=70以上 堆內存達到多少比例時,觸發CMS。需要與-XX:+UseCMSInitiatingOccupancyOnly 同時使用以保證CMS不自動觸發,否則不到比例也可能觸發CMS.
計算公式一:CMSInitiatingOccupancyFraction <=((Xmx-Xmn)-(Xmn-Xmn/(SurvivorRatior+2)))/(Xmx-Xmn)*100
考慮了2個救助區中一個servior不會使用的情況。
計算公式二:(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn
統一考慮年輕代。
滿足以上2個計算公式,則不會出現 concurrent model fail。及不會出現並行回收失敗,降級爲虛擬機串行回收(SerialGC)的情況。
7.CMS碎片問題
因爲年老代的併發收集器使用標記,清除算法,所以不會對堆進行壓縮.當收集器回收時,他會把相鄰的空間進行合併,這樣可以分配給較大的對象.但是,當堆空間較小時,運行一段時間以後,就會出現"碎片",如果併發收集器找不到足夠的空間,那麼併發收集器將會停止,然後使用傳統的標記,清除方式進行回收.如果出現"碎片",可能需要進行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用併發收集器時,開啓對年老代的壓縮.
-XX:CMSFullGCsBeforeCompaction=0:上面配置開啓的情況下,這裏設置多少次Full GC後,對年老代進行壓縮
8.XMX和XMS設置一樣大,MaxPermSize和MinPermSize設置一樣大,這樣可以減輕伸縮堆大小帶來的壓力,還真有人不這麼幹
9:promotion failed:
救助空間不足,要放到老生代,但老生代空閒空間存在碎片,導致沒有足夠大的連續空間來存放新生代對象的升級時,機會觸發promotion failed
解決方法:
9.1.增大救助空間、增大年老代
增大救助空間就是調小-XX:SurvivorRatio 這個參數是Eden區和Survivor區的大小比值,默認是32
增大年老代,調大-Xmn參數,增加新生代,或調大-XX:NewRatio,減少新生代比例,增大年老代。
9.2..去掉救助空間(不贊成)
調大-XX:SurvivorRatio到非常大的數值65536
10:Concurrent Mode Failure
併發收集器在應用運行時進行收集,所以需要保證堆在垃圾回收的這段時間有足夠的空間供程序使用,否則,垃圾回收還未完成,堆空間先滿了。這種情況下將會發生“併發模式失敗”,此時整個應用將會暫停,進行垃圾回收
解決方法:
10.1.滿足第6點的公式,可以避免這個問題。
10.2.調小CMSMaxAbortablePrecleanTime的值 儘快回收
11.CMS默認啓動的回收線程數目是 (ParallelGCThreads + 3)/4)
也可以通過:-XX:ParallelCMSThreads=20
設置
12.爲了減少CMS GC第二次暫停的時間,開啓並行remark: -XX:+CMSParallelRemarkEnabled。如果remark還是過長的話,可以開啓-XX:+CMSScavengeBeforeRemark選項,強制remark之前開始一次minor gc,減少remark的暫停時間,但是在remark之後也將立即開始又一次minor gc。
13.+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled CMS回收持久代
14.FullGC的觸發條件
14.1.old空間不足 達到比例(CMSInitiatingOccupancyFraction) 或 promotion failed 或 current mode failed(這個會很悲劇)
14.2.Perm空間不足 92%
14.3.顯示調用System.GC, RMI等的定時觸發
14.4.YGC時的悲觀策略
14.5.dump live的內存信息時(jmap –dump:live)
15。YGC時的悲觀策略
15.1、在YGC執行前,min(目前新生代已使用的大小,之前平均晉升到old的大小中的較小值) > 舊生代剩餘空間大小 ? 不執行YGC,直接執行Full GC: 執行YGC;15.2、在YGC執行後,平均晉升到old的大小 > 舊生代剩餘空間大小 ? 觸發Full GC : 什麼都不做CMSGc時,舊生代剩餘空間需要考慮CMSInitiatingOccupancyFraction
在Minor GC觸發時,會檢測之前每次晉升到老年代的平均大小是否大於老年代的剩餘空間,如果大於,改爲直接進行一次Full GC,如果小於則查看HandlePromotionFailure設置看看是否允許擔保失敗,如果允許,那仍然進行Minor GC,如果不允許,則也要改爲進行一次Full GC
16.導致OOM的原因
1.GC overhead limit exceeded
2.Java Heap Space
3.Unable to create new native thread
4.PermGen Space
5.Direct buffer memory
6.request {} bytes for {}. Out of swap space?
四 案例及日誌
CMS GC的一個具體案例:
具體過程爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
2013 - 11 -27T04: 00 : 12.819 + 0800 : 38892.743 :
[GC [ 1 CMS-initial-mark: 1547313K(2146304K)] 1734957K(4023680K), 0.1390860 secs]
[Times: user= 0.14 sys= 0.00 ,
real= 0.14 secs] 2013 - 11 -27T04: 00 : 12.958 + 0800 : 38892.883 :
[CMS-concurrent-mark-start] 2013 - 11 -27T04: 00 : 19.231 + 0800 : 38899.155 :
[CMS-concurrent-mark: 6.255 / 6.272 secs]
[Times: user= 8.49 sys= 1.57 ,
real= 6.27 secs] 2013 - 11 -27T04: 00 : 19.231 + 0800 : 38899.155 :
[CMS-concurrent-preclean-start] 2013 - 11 -27T04: 00 : 19.250 + 0800 : 38899.175 :
[CMS-concurrent-preclean: 0.018 / 0.019 secs]
[Times: user= 0.02 sys= 0.00 ,
real= 0.02 secs] 2013 - 11 -27T04: 00 : 19.250 + 0800 : 38899.175 :
[CMS-concurrent-abortable-preclean-start] CMS:
abort preclean due to time 2013 - 11 -27T04: 00 : 25.252 + 0800 : 38905.176 :
[CMS-concurrent-abortable-preclean: 5.993 / 6.002 secs]
[Times: user= 6.97 sys= 2.16 ,
real= 6.00 secs] 2013 - 11 -27T04: 00 : 25.253 + 0800 : 38905.177 :
[GC[YG occupancy: 573705 K ( 1877376 K)] 38905.177 :
[Rescan (parallel) , 0.3685690 secs] 38905.546 :
[weak refs processing, 0.0024100 secs] 38905.548 :
[cla ss unloading, 0.0177600 secs] 38905.566 :
[scrub symbol & string tables, 0.0154090 secs] [ 1 CMS-remark:
1547313K(2146304K)] 2121018K(4023680K), 0.4229380 secs] [Times:
user= 1.41 sys= 0.01 ,
real= 0.43 secs] 2013 - 11 -27T04: 00 : 25.676 + 0800 : 38905.601 :
[CMS-concurrent-sweep-start] 2013 - 11 -27T04: 00 : 26.436 + 0800 : 38906.360 :
[CMS-concurrent-sweep: 0.759 / 0.760 secs]
[Times: user= 1.06 sys= 0.48 ,
real= 0.76 secs] 2013 - 11 -27T04: 00 : 26.436 + 0800 : 38906.360 :
[CMS-concurrent-reset-start] 2013 - 11 -27T04: 00 : 26.441 + 0800 : 38906.365 :
[CMS-concurrent-reset: 0.005 / 0.005 secs]
[Times: user= 0.00 sys= 0.00 ,
real= 0.00 secs]
這個是一個正常的CMS的日誌,共分爲七個步驟,重點關注initial-mark和remark這兩個階段,因爲這兩個是停機的。 A、[GC [1 CMS-initial-mark: 1547313K(2146304K)] 1734957K(4023680K), 0.1390860 secs] [Times: user=0.14 sys=0.00, real=0.14 secs] 各個數據依次表示標記前後old區的所有對象佔內存大小和old的capacity,整個JavaHeap(不包括perm)所有對象佔內存總的大小和JavaHeap的capacity。 B、2013-11-27T04:00:25.253+0800: 38905.177: [GC[YG occupancy: 573705 K (1877376 K)]38905.177: [Rescan (parallel) , 0.3685690 secs]38905.546: [weak refs processing, 0.0024100 secs]38905.548: [class unloading, 0.0177600 secs]38905.566: [scrub symbol & string tables, 0.0154090 secs] [1 CMS-remark: 1547313K(2146304K)] 2121018K(4023680K), 0.4229380 secs] [Times: user=1.41 sys=0.01, real=0.43 secs] Rescan (parallel)表示的是多線程處理young區和多線程掃描old+perm的卡表的總時間, parallel 表示多GC線程並行。 weak refs processing 處理old區的弱引用的總時間,用於回收native memory。 class unloading 回收SystemDictionary消耗的總時間 |
a)首先jvm根據-XX:CMSInitiatingOccupancyFraction,-XX:+UseCMSInitiatingOccupancyOnly 來決定什麼時間開始垃圾收集
b)如果設置了-XX:+UseCMSInitiatingOccupancyOnly,那麼只有當old代佔用確實達到了 -XX:CMSInitiatingOccupancyFraction參數所設定的比例時纔會觸發cms gc
c)如果沒有設置-XX:+UseCMSInitiatingOccupancyOnly,那麼系統會根據統計數據自行決定什麼時候 觸發cms gc;因此有時會遇到設置了80%比例才cms gc,但是50%時就已經觸發了,就是因爲這個參數 沒有設置的原因
d)當cms gc開始時,首先的階段是CMS-initial-mark,此階段是初始標記階段,是stop the world階段, 因此此階段標記的對象只是從root集最直接可達的對象
CMS-initial-mark:961330K(1572864K),指標記時,old代的已用空間和總空間
e)下一個階段是CMS-concurrent-mark,此階段是和應用線程併發執行的,所謂併發收集器指的就是這個, 主要作用是標記可達的對象
此階段會打印2條日誌:CMS-concurrent-mark-start,CMS-concurrent-mark
f)下一個階段是CMS-concurrent-preclean,此階段主要是進行一些預清理,因爲標記和應用線程是併發執行的, 因此會有些對象的狀態在標記後會改變,此階段正是解決這個問題
因爲之後的Rescan階段也會stop the world,爲了使暫停的時間儘可能的小,也需要preclean階段先做一部分 工作以節省時間
此階段會打印2條日誌:CMS-concurrent-preclean-start,CMS-concurrent-preclean
g)下一階段是CMS-concurrent-abortable-preclean階段,加入此階段的目的是使cms gc更加可控一些, 作用也是執行一些預清理,以減少Rescan階段造成應用暫停的時間
此階段涉及幾個參數:
-XX:CMSMaxAbortablePrecleanTime:當abortable-preclean階段執行達到這個時間時纔會結束
-XX:CMSScheduleRemarkEdenSizeThreshold(默認2m):控制abortable-preclean階段什麼時候開始執行,即當eden使用達到此值時,纔會開始abortable-preclean階段
-XX:CMSScheduleRemarkEdenPenetratio(默認50%):控制abortable-preclean階段什麼時候結束執行
此階段會打印一些日誌如下:
CMS-concurrent-abortable-preclean-start,CMS-concurrent-abortable-preclean,CMS:abort preclean due to time XXX
h)再下一個階段是第二個stop the world階段了,即Remark階段,此階段暫停應用線程,對對象進行重新掃描並 標記
YG occupancy:964861K(2403008K),指執行時young代的情況
CMS remark:961330K(1572864K),指執行時old代的情況
此外,還打印出了弱引用處理、類卸載等過程的耗時
i)再下一個階段是CMS-concurrent-sweep,進行併發的垃圾清理
j)最後是CMS-concurrent-reset,爲下一次cms gc重置相關數據結構
一些日誌:
、promotion failed的一段日誌
1
2
|
2013 - 11 -27T03: 00 : 53.638 + 0800 : 35333.562 :
[GC 35333.562 : [ParNew (promotion failed): 1877376K->1877376K(1877376K), 15.7989680 secs] 35349.361 :
[CMS: 2144171K->2129287K(2146304K), 10.4200280 sec s] 3514052K->2129287K(4023680K), [CMS Perm :
119979K->118652K(190132K)], 26.2193500 secs] [Times: user= 30.35 sys= 5.19 ,
real= 26.22 secs] |
解釋如下:
1
2
3
4
5
|
1877376K->1877376K(1877376K), 15.7989680 secs
young區 2144171K->2129287K(2146304K), 10.4200280 sec
old區情況 3514052K->2129287K(4023680K)
heap區情況 119979K->118652K(190132K)], 26.2193500 secs
perm區情況 [Times: user= 30.35 sys= 5.19 ,
real= 26.22 secs] 整個過程的時間消耗 |
一段正常的Young GC的日誌
1
2
|
2013 - 11 -27T04: 00 : 07.345 + 0800 : 38887.270 :
[GC 38887.270 : [ParNew: 1791076K->170624K(1877376K), 0.2324440 secs]
2988366K->1413629K(4023680K), 0.2326470 secs] [Times: user= 0.80 sys= 0.00 ,
real= 0 . 23 secs] |
ParNew這個表明是並行的回收方式,具體的分別是young區、整個heap區的情況;
一段通過system.gc產生的FullGC日誌
1
|
2013 - 07 -21T17: 44 : 01.554 + 0800 : 50.568 :
[Full GC (System) 50.568 : [CMS: 943772K->220K(2596864K), 2.3424070 secs]
1477000K->220K(4061184K), [CMS Perm : 3361K->3361K(98304K)], 2.3425410 secs]
[Times: user= 2.33 sys= 0.01 ,
real= 2.34 secs] |
解釋如下:
Full GC (System)意味着這是個system.gc調用產生的MSC。
“943772K->220K(2596864K), 2.3424070 secs”表示:這次MSC前後old區內總對象大小,old的capacity及這次MSC耗時。
“1477000K->220K(4061184K)”表示:這次MSC前後JavaHeap內總對象大小,JavaHeap的capacity。
“3361K->3361K(98304K)], 2.3425410 secs”表示:這次MSC前後Perm區內總對象大小,Perm區的capacity。
一個特殊的GC日誌,根據動態計算直接進行的FullGC(CMS的方式)悲觀策略
1
|
2013 - 03 -13T13: 48 : 06.349 + 0800 : 7.092 :
[GC 7.092 : [ParNew: 471872K->471872K(471872K), 0.0000420 secs] 7.092 :
[CMS: 366666K->524287K(524288K), 27.0023450 secs] 838538K->829914K(996160K),
[CMS Perm : 3196K->3195K(131072K)], 27.0025170 secs] |
ParNew的時間特別短,jvm在minor gc前會首先確認old是不是足夠大,如果不夠大,這次young gc直接返回,進行MSC。
參考文檔:
http://hi.baidu.com/576699909/item/9daf4d7c33a09f316f29f66c
http://www.viluo.com/post/25
http://blog.csdn.net/fycghy0803/article/details/6679827
http://www.cnblogs.com/redcreen/archive/2011/05/04/2037029.html
http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html#CMSInitiatingOccupancyFraction_value
http://blog.csdn.net/jiasanshou/article/details/24908567
http://blog.csdn.net/jiasanshou/article/details/24908371
http://blog.csdn.net/jiasanshou/article/details/24908187
http://blog.csdn.net/calvinxiu/article/details/1614473
http://guoliangqi.iteye.com/blog/630692
http://www.blogjava.net/BlueDavy/archive/2009/10/09/297562.html
http://www.iteye.com/topic/473874
http://hllvm.group.iteye.com/group/wiki/2870-JVM
http://ee.riaos.com/?p=20009582