1. 程式人生 > >Java GC相關知識

Java GC相關知識

單位 art 一次 init 完成 com 持久 static 延遲

Java堆的分類

  • 分為兩類:YoungGen和OldGen。其中,YoungGen分為三部分:eden,from survivor和to survivor,比例默認是:8:1:1
  • PermGen不屬於java堆的範疇

    需要註意的是,從java8開始,PermGen已經被取消,取而代之的是metaspace,不同點在於:PermGen包含class metadata,class static variable和interned string,但是metaspace只有class metadata,另外兩個(class static variable和interned string)從java8開始被移動到java堆裏面

GC的分類

針對HotSpot VM的實現,它裏面的GC其實準確分類只有兩大種:

  • Partial GC:並不收集整個GC堆的模式,目前主要分為下面三類:

Young GC:只收集young gen的GC
Old GC:只收集old gen的GC。CMS的concurrent collection就是這個模式
Mixed GC:收集整個young gen以及部分old gen的GC。只有G1有這個模式

  • Full GC:收集整個堆,包括young gen、old gen、perm gen(如果存在的話)等所有部分的模式。Full GC進行前默認會進行一次young GC,可通過參數-XX:+ScavengeBeforeFullGC來進行配置

  • 各種GC算法及其使用方法

Young collectorOld collectorJVM option
Serial (DefNew) Serial Mark-Sweep-Compact -XX:+UseSerialGC
Parallel scavenge (PSYoungGen) Serial Mark-Sweep-Compact(PSOldGen) -XX:+UseParallelGC
Parallel scavenge (PSYoungGen) Parallel Mark-Sweep-Compact(ParOldGen) -XX:+UseParallelOldGC
Serial (DefNew) Concurrent Mark Sweep -XX:+UseConcMarkSweepGC
-XX:-UseParNewGC
Parallel (ParNew) Concurrent Mark Sweep -XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
G1算法適用於年輕代和老年代,使用-XX:+UseG1GC來啟用

GC的觸發條件

1. Young GC觸發條件

  • Eden區滿了,會進行Young GC,將Eden和from中存活的對象放入到to中。

2. Full GC觸發條件

  • 調用System.gc時,系統建議執行Full GC,但是不必然執行
  • oldGen最大【連續空間】不足,當新生代對象轉入到老年代時,如果有大對象或者大數組,那麽會出現老年代空間不足的情況;
  • PermGen空間不足
  • CMS GC時出現promotion failed和concurrent mode failure
    promotion failed是在進行Minor GC時,survivor space放不下、對象只能放入舊生代,而此時舊生代【最大連續空間】也不足以放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有對象要放入舊生代,而此時舊生代空間不足造成的。
  • 統計得到的Minor GC晉升到舊生代的平均大小大於舊生代的剩余空間
  • 對於使用RMI來進行RPC或管理的Sun JDK應用而言,默認情況下會一小時執行一次Full GC。可通過在啟動時通過- java -Dsun.rmi.dgc.client.gcInterval=3600000來設置Full GC執行的間隔時間或通過-XX:+ DisableExplicitGC來禁止RMI調用System.gc。

CMS GC和G1 GC介紹

1. CMS GC收集過程(CMS GC是針對於老年代的GC算法)

  • CMS Initial Mark(stop-the-world):收集階段,開始收集所有的GC Roots和直接引用到的對象【該階段會被統計到full gc裏面】;
  • CMS-concurrent-mark:這個階段會遍歷整個老年代並且標記所有存活的對象,從“初始化標記”階段找到的GC Roots開始。並發標記的特點是和應用程序線程同時運行。並不是老年代的所有存活對象都會被標記,因為標記的同時應用程序會改變一些對象的引用等。
  • CMS-concurrent-preclean:這個階段又是一個並發階段,和應用線程並行運行,不會中斷他們。前一個階段在並行運行的時候,一些對象的引用已經發生了變化,當這些引用發生變化的時候,JVM會標記堆的這個區域為Dirty Card(包含被標記但是改變了的對象,被認為"dirty"),這就是 Card Marking。
  • CMS-concurrent-abortable-preclean:又一個並發階段不會停止應用程序線程。這個階段嘗試著去承擔STW的Final Remark階段足夠多的工作。這個階段持續的時間依賴好多的因素,由於這個階段是重復的做相同的事情直到發生aboart的條件(比如:重復的次數、多少量的工作、持續的時間等等)之一才會停止。
  • CMS Final Remark(stop-the-world):這個階段是CMS中第二個並且是最後一個STW的階段。該階段的任務是完成標記整個年老代的所有的存活對象。由於之前的預處理是並發的,它可能跟不上應用程序改變的速度,這個時候,STW是非常需要的來完成這個嚴酷考驗的階段。【該階段會被統計到full gc裏面】;
通常CMS盡量運行Final Remark階段在年輕代是足夠幹凈的時候,目的是消除緊接著的連續的幾個STW階段。
  • CMS-concurrent-sweep:和應用線程同時進行,不需要STW。這個階段的目的就是移除那些不用的對象,回收他們占用的空間並且為將來使用。
  • CMS-concurrent-reset:這個階段並發執行,重新設置CMS算法內部的數據結構,準備下一個CMS生命周期的使用。

2. G1 GC(新生代和老年代通用的收集算法,不需要其他收集算法配合)

  • 【後續將進行補充】

一些JVM參數調整的經驗和規則

  1. 年輕代大小選擇

  • 響應時間優先的應用:盡可能設大,直到接近系統的最低響應時間限制(根據實際情況選擇).在此種情況下,年輕代收集發生的頻率也是最小的.同時,減少到達年老代的對象.
  • 吞吐量優先的應用:盡可能的設置大,可能到達Gbit的程度.因為對響應時間沒有要求,垃圾收集可以並行進行,一般適合8CPU以上的應用.
  • 避免設置過小.當新生代設置過小時會導致:1.YGC次數更加頻繁 2.可能導致YGC對象直接進入舊生代,如果此時舊生代滿了,會觸發FGC.
  1. 年老代大小選擇

  • 響應時間優先的應用:年老代使用並發收集器,所以其大小需要小心設置,一般要考慮並發會話率和會話持續時間等一些參數.如果堆設置小了,可以會造成內存碎 片,高回收頻率以及應用暫停而使用傳統的標記清除方式;如果堆大了,則需要較長的收集時間.最優化的方案,一般需要參考以下數據獲得: 並發垃圾收集信息、持久代並發收集次數、傳統GC信息、花在年輕代和年老代回收上的時間比例。
  • 吞吐量優先的應用:一般吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代.原因是,這樣可以盡可能回收掉大部分短期對象,減少中期的對象,而年老代盡存放長期存活對象.
  • 較小堆引起的碎片問題 因為年老代的並發收集器使用標記,清除算法,所以不會對堆進行壓縮.當收集器回收時,他會把相鄰的空間進行合並,這樣可以分配給較大的對象.但是,當堆空間較小時,運行一段時間以後,就會出現"碎片",如果並發收集器找不到足夠的空間,那麽並發收集器將會停止,然後使用傳統的標記,清除方式進行回收.如果出現"碎片",可能需要進行如下配置: -XX:+UseCMSCompactAtFullCollection:使用並發收集器時,開啟對年老代的壓縮. -XX:CMSFullGCsBeforeCompaction=0:上面配置開啟的情況下,這裏設置多少次Full GC後,對年老代進行壓縮
  • 使用CMS的好處是用盡量少的新生代,經驗值是128M-256M, 然後老生代利用CMS並行收集, 這樣能保證系統低延遲的吞吐效率。 實際上cms的收集停頓時間非常的短,2G的內存, 大約20-80ms的應用程序停頓時間

關於一些參數

  • -XX:MaxTenuringThreshold=n Sets the maximum tenuring threshold for use in adaptive GC sizing. The current largest value is 15. The default value is 15 for the parallel collector and is 4 for CMS.(需要註意的是,這個參數默認為15,但是對於CMS來講,默認為4,該段文字摘自官方文檔)
  • -XX:+UseCMSCompactAtFullCollection:CMS使用標記-清除法進行垃圾回收,因此不對內存隨便進行整理,使用該選項可以指定對內存碎片進行整理,該選項默認是開啟的
  • -XX:+ScavengeBeforeFullGC:指定進行fullGC前進行一次young GC
  • -XX:CMSInitiatingOccupancyFraction:CMS被觸發時老年代使用的比例
  • -XX:MaxGCPauseMillis=50:一次GC的最大時間,單位為ms,使用parallel scanvenge算法和G1的時候才會有效
  • -XX:PretenureSizeThreshold:超過設定的大小,那麽對象將會直接被分配到老年代。單位為byte,默認為0,不開啟該功能。(對於PS的收集算法,該選項無效)
  • -XX:+HandlerPromotionFailure:在Minore GC前,jvm會預估老年代最大可用的連續空間是否大於新生代所有對象總空間,如果小於,那麽如果打開此開關,jvm會計算老年代最大可用的連續空間是否大於【歷代】年輕代晉升到老年代所有對象的平均大小,如果小於,那麽會進行Minore GC,否則,進行full GC; 如果此開關沒有打開,那麽會直接進行full GC,(目前根據jdk源碼,該選項已經無效,jvm會直接進行上述的判斷)

GC常見的幾個誤解:

  • 除了CMS和G1外,PSYoung Gen,DefNew,PSOldGen,ParOldGen等收集算法都需要stw。
  • STW(stop-the-world)並不等於full gc,full gc指發生在年輕代和老年代的gc。
  • CMS是發生在老年代的GC算法,但是其中的兩個階段,initial marking和final remark發生在年輕代和老年代,因此其stw屬於full gc的統計數據裏。
  • 當CMS運行過程中,老年代空間不夠,默認會使用Serial gc進行一次full gc。

參考資料

  • https://www.zhihu.com/question/41922036/answer/93079526 RednaxelaFX的回答
  • http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
  • http://ifeve.com/useful-jvm-flags-part-7-cms-collector

Java GC相關知識