JVM基于分代的垃圾收集器

lpkde 9年前發布 | 15K 次閱讀 JVM Java開發

JVM發展到今天,垃圾回收器已經有很多種,像標記-清除,標記-壓縮,復制等,各有各的優缺點。在這里主要將其中的一種,基于分代的垃圾收集器。

基于分代的垃圾收集器的算法設計思路是:把對象按照壽命長短來分組,新創建的被劃分在年輕代,經過幾次回收后仍然存活的劃分在年老代。這樣減少了每次垃圾收集時所要掃描的對象的數量,從而提高了垃圾回收效率

JVM將Java堆分為兩個區域,Young區和Old區。Young區又分為Eden,Survivor From,Survivor To三個子區;其中所有的新創建對象都在Eden區;當Eden區滿后會觸發minor GC,將Eden區仍然存活的對象復制到其中的一個Survivor中,另一個Survivor區中存活的對象也復制到這個Survivor中,以保證始終有一個Survivor區是空的。Old區存放的是Young區的Survivor滿后觸發minor GC后仍然存活的對象。當Eden區滿時,會將對象存放到Survivor區中,如果Survivor去仍然存不下這些對象,GC收集器會將這些對象直接存放到Old區,如果Old區也滿了,則觸發Full GC,回收整個堆內存。在Sun的JVM中提供了visualvm(bin目錄下的jvisualvm.exe)工具,其中有一個Visual GC插件可以看到JVM的不同代垃圾回收情況。
(Sun對堆中的不同代大小劃分建議是,Young區為整個堆的1/4;而Young區中Eden:Survivor=8:2(from,to兩個區1:1))

對于分代垃圾回收器,主要有三種:Serial Collector,Parallel Collector,CMS Collector。

Serial Collector(串行回收器)是JVM在client模式下默認的GC模式。可以通過JVM配置參數 -XX:+UseSerialGC 來指定GC使用該收集算法。由于是單線程GC工作,所以無論Minor GC還是Full GC,都會造成應用程序的全部停止。在Full GC中,會對整個Old區進行壓縮。
在創建新對象時,如果新對象的大小超過Eden區的總大小,或者超過了PretenureSizeThreshold配置參數配置的大小,就只能在Old區分配。
當 Eden空間不足時,首先檢查每次Minor GC時復制到Old區的平均對象大小是否大于Old的剩余空間,如果大于,則直接觸發Full GC,否則,再看HandlePromotionFailure(-XX:-HandlePromotionFailure)的值,如果為true,則觸發Minor GC,否則,觸發Minor GC后再觸發Full GC。也就是說,如果每次需要復制的對象的大小超過了Old的剩余空間大小,就說明當前Old區的剩余空間大小已經不能滿足Minor GC時從Eden,Survivor區復制過來的對象的存放空間了,所以只能觸發Full GC。
我們知道,在Minor GC中,除了回收Eden去的非活動對象外,還會把一些“老對象”復制到Old區,而“老對象”的定義可以通過配置參數 MaxTenuringThreshold來設置,如設置 -XX:MaxTenuringThreshold=10,則如果一個對象已經經歷了10次Minor GC后仍然存活在Young區,則下次Minor GC時,直接將這個對象復制到Old區。還有一種情況是,如果Minor GC時,Survivor區存放不下這些將要存放到Survivor區的對象時,也會將這些對象復制到Old區;如果Old區空間不足,則觸發Full GC,Full GC會清除堆中的所有垃圾對象

Parallel Collector(并行回收器)(throughput collector):使用多線程的方式,利用多CUP來提高GC的效率,主要以到達一定的吞吐量為目標;Server模式下默認GC模式。 Parallel Collector根據Minor GC,Full GC的不同分為三種,分別是 ParNewGC,ParallelGC,ParallelOldGC
1)ParNewGC可以通過-XX:+UseParNewGC參數來指定,它的對象分配和回收策略與Serial Collector類似,只是回收的線程是多線程并行回收的。在Parallel Collector中還有一個UseAdaptiveSizePolicy配置,這個參數用來動態控制Eden,Survivor From ,Survivor To的TenuringThreshold大小的,以便控制那些對象經過多少次回收后可以直接放入Old區
2)ParallelGC 在Server模式下默認的GC模式,可以通過 -XX:UseParallelGC參數來強制指定,并行回收的線程數可以通過 -XX:ParallelGCThreads來指定,這個值有一個計算公式,如果CPU核數小于8,線程數可以和核數一樣;否則,值可以設置為 3+(cpu_core5)/8。通過-Xmn來設置Young區的大小,通過SurvivorRatio參數控制Eden,Survivor From,Survivor To的大小比例,如 -XX:SurvivorRatio=8 則表示Eden:Survivor From:Survivor To=8:1:1,其默認值也是8。
當在Eden區中申請內存空間時,如果Eden區剩余空間不夠,則看當前申請的空間是否大于等于Eden總空間的一半,如果大于,則直接在Old中分配,如果小于,則觸發Minor GC,觸發前首先檢查每次Minor GC時復制到Old區的平均大小是否大于Old的剩余空間,如果大于,則再觸發Full GC。此次觸發GC后仍然會按照這個規則重新檢查一次,如果仍然滿足,Full GC會再一次觸發。
當Old區不足時,會觸發Full GC,如果配置了ScavengeBeforeFullGC,則在Full GC之前,會先觸發Minor GC
3)ParallelOldGC 可以通過-XX:+UseParallelOldGC來設置,并行回收的線程數可以通過 -XX:ParallelGCThreads來指定,這個值有一個計算公式,如果CPU核數小于8,線程數可以和核數一樣;否則,值可以設置為 3+(cpu_core
5)/8。它與ParallelGC不同之處在于,前者Full GC會清除整個堆的垃圾對象,而后者只清楚部分,并對部分空間壓縮。GC時同樣也會造成應用程序的全部停止。

CMS Collector(并發收集器),可以通過-XX:+UseConcMarkSweepGC來指定,并發的線程數默認為4,也可以通過ParallelCMSThreads來指定。
CMS GC和上面討論的GC不太一樣。它既不是Minor GC,也不是Full GC,而是基于二者之間的一種GC,它的觸發規則是檢查Old區或者Perm的使用率,當比例達到一定值時觸發CMS GC,回收Old區內存空間。這個比例可以通過CMSInitiatingOccupancyFraction參數來指定,默認為92%。這個默認值是通過((100-MinHeapFreeRatio)+(double)(CMSTriggerRatioMinHeapFreeRatio)/100.0) /100.0計算出來的,其中MinHeapFreeRatio=40,CMSTriggerRatio=80。如果Perm區也使用CMS GC,可以通過-XX:+CMSClassUnloadingEnabled設置,默認值也是92%,也是通過公式((100- MinHeapFreeRatio)+(double)(CMSTriggerPermRatioMinHeapFreeRatio)/100.0)/100.0,其中MinHeapFreeRatio=40,CMSTriggerPermRatio=80
觸發CMS GC時回收的只是Old區和Perm區的垃圾對象,和Minor GC,Full GC基本沒有關系。在這種模式下,Minor GC的觸發規則和回收規則與Serial Collector基本一致,不同之處在于此GC回收為多線程。而觸發Full GC有兩種情況,一種是Eden區分配失敗,Minor GC后分配到Survivor區時Survivor不夠,Old區也不夠。另一種情況是當CMS GC正在進行時,向Old區申請內存失敗。
在hotspot1.6中如果使用了這種GC方式,在程序中顯示的調用System.gc(),且設置了ExplicitGCInvokesConcurrent參數,那么在使用NIO時可能會引發內存泄漏。
CMS GC何時執行JVM還會有一些時機選擇,如當前CPU是否繁忙。因此它有一個計算規則,并根據這個規則來動態調整。這也給JVM帶來了一些開銷,如果要禁止這個動態調整,禁止CMS GC自動觸發,則可以配置參數 -XX:+UseCMSInitiatingOccuoancyOnly來實現

 本文由用戶 lpkde 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!