淺析JVM的垃圾回收器
JVM的GC經(jīng)過多年的發(fā)展,大家對Minor GC、major GC的理解并不完全一致,所以我不打算在本文中使用這個概念。我把GC大概分為一下4類:
- Young GC:只是負責回收年輕代對象的GC;
- Old GC:只是負責回收老年代對象的GC;
- Full GC:回收整個堆的對象,包括年輕代、老年代、持久帶;
- Mixed GC:回收年輕代和部分老年代的GC (G1);
因為筆者目前使用G1還是比較少的,所以本文不打算將G1。
垃圾回收器算法
目前主流垃圾回收器都采用的是可達性分析算法來判斷對象是否已經(jīng)存活,不使用引用計數(shù)算法判斷對象時候存活的原因在于該算法很難解決相互引用的問題。
標記-清除算法(Mark-Sweep)
標記-清除算法由標記階段和清除階段構(gòu)成。標記階段是把所有活著的對象都做上標記的階段;清除階段是把那些沒有標記的對象,也就是非活動對象回收的階段。通過這兩個階段,就可以令不能利用的內(nèi)存空間重新得到利用。
從標記-清除算法我們可以看出,該算法不涉及對象移動,但是可能會產(chǎn)生內(nèi)存碎片化問題。空間碎片太高可能會導(dǎo)致程序運行時需要分配較大內(nèi)存時候,無法找到足夠的連續(xù)內(nèi)存,需要其他垃圾回收幫助回收內(nèi)存。
復(fù)制算法(Copying)
復(fù)制算法內(nèi)存空間分為兩塊區(qū)域:From、to,每次只使用其中一塊,在垃圾回收時將正在使用的內(nèi)存中的存活對象復(fù)制到未被使用的內(nèi)存塊中,之后,清除正在使用的內(nèi)存塊中的所有對象,交換兩個內(nèi)存的角色,完成垃圾回收。
上面那種復(fù)制算法有一半的空間是浪費的。所以在Java新生代把內(nèi)存區(qū)域分為Eden空間、from、to空間3個部分,from和to空間也稱為survivor 空間,用于存放未被回收的對象。對象開始都是Eden生成;當回收時,將Eden和from中存活的對象移動到to區(qū)域中。
復(fù)制算法存在空間浪費的情況,始終都要保持一個Survivor是空閑的,并且在GC的時候要是存活對象大小超過了Survivor中的大小,就需要另外的策略存儲存活對象。
目前open JDK新生代回收策略就是采用的復(fù)制算法,其中Eden和Survivor的默認配置為8:1
標記-壓縮算法(Mark-Compact)
標記-壓縮算法由標記階段和壓縮階段構(gòu)成。標記階段標記-清除算法中的標記階段完全一樣,壓縮階段是讓所有存活的對象向一端移動。這樣空閑內(nèi)存都在另外一端,屬于連續(xù)空間,不存在內(nèi)存碎片化問題,但是會產(chǎn)生對象移動。
分代算法(Generational GC)
根據(jù)對象的不同生命周期分別管理, JVM 中將對象分為我們熟悉的新生代、老年代和永久代分別管理。這樣做的好處就是可以根據(jù)不同類型對象進行不同策略的管理,例如新生代中對象更新速度快,就會使用效率較高的復(fù)制算法。老年代中內(nèi)存空間相對分配較大,而且時效性不如新生代強,就會常常使用Mark-Sweep-Compact (標記-清除-壓縮)算法。
各種算法性能比較
常見的垃圾回收器
垃圾回收器分類
總體上可以把Java的垃圾回收器分為3類:
- 串行垃圾回收器(Serial Garbage Collector)
- 并行垃圾回收器(Parallel Garbage Collector)
- 并發(fā)標記掃描垃圾回收器(CMS Garbage Collector)
Java垃圾回收器主要有6種,各自優(yōu)缺點以及組合關(guān)系如下:
其中的連線表示young gc和old gc可以搭配使用
垃圾回收器選擇策略:
- 客戶端程序:Serial + Serial Old;
- 吞吐率優(yōu)先的服務(wù)端程序(比如:計算密集型):Parallel Scavenge + Parallel Old;
- 響應(yīng)時間優(yōu)先的服務(wù)端程序:ParNew + CMS。
目前很大一部分的Java應(yīng)用都集中在互聯(lián)網(wǎng)的服務(wù)器端,這類應(yīng)用尤其關(guān)系服務(wù)的響應(yīng)時間,希望應(yīng)用暫停時間更短,所以基本上使用的都是ParNew + CMS,這也是我司默認使用的配置。
CMS垃圾回收器
在啟動JVM參數(shù)加上 -XX:+UseConcMarkSweepGC ,這個參數(shù)表示對于老年代的回收采用 CMS。
CMS執(zhí)行過程
CMS 的回收過程主要分為下面的幾個步驟:
- 初始標記(Initial Mark)
- 并發(fā)標記(Concurrent marking)
- 并發(fā)預(yù)清理(Concurrent pre-preclean)
- 重新標記(Final Remark)
- 并發(fā)清理(Concurrent sweep)
- 并發(fā)重置(Concurrent reset)
標準的CMS日志如下:
2018-11-10T18:23:27.531+0800: 1495270.652: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2008820K(2510848K)] 2038212K(4398336K), 0.0231086 secs] [Times: user=0.01 sys=0.00, real=0.03 secs] 2018-11-10T18:23:27.554+0800: 1495270.675: [CMS-concurrent-mark-start] 2018-11-10T18:23:27.644+0800: 1495270.765: [CMS-concurrent-mark: 0.090/0.090 secs] [Times: user=0.34 sys=0.03, real=0.09 secs] 2018-11-10T18:23:27.644+0800: 1495270.765: [CMS-concurrent-preclean-start] 2018-11-10T18:23:27.654+0800: 1495270.775: [CMS-concurrent-preclean: 0.010/0.010 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 2018-11-10T18:23:27.655+0800: 1495270.775: [CMS-concurrent-abortable-preclean-start] 2018-11-10T18:23:32.305+0800: 1495275.425: [CMS-concurrent-abortable-preclean: 4.623/4.650 secs] [Times: user=7.01 sys=1.01, real=4.65 secs] 2018-11-10T18:23:32.307+0800: 1495275.427: [GC (CMS Final Remark) [YG occupancy: 847369 K (1887488 K)]1495275.427: [Rescan (parallel) , 0.0902177 secs]1495275.518: [weak refs processing, 0.0514433 secs]1495275.569: [class unloading, 0.0256119 secs]1495275.595: [scrub symbol table, 0.0074695 secs]1495275.602: [scrub string table, 0.0015014 secs][1 CMS-remark: 2008820K(2510848K)] 2856190K(4398336K), 0.1806988 secs] [Times: user=0.68 sys=0.00, real=0.18 secs] 2018-11-10T18:23:32.488+0800: 1495275.609: [CMS-concurrent-sweep-start] 2018-11-10T18:23:33.660+0800: 1495276.781: [CMS-concurrent-sweep: 1.172/1.172 secs] [Times: user=1.89 sys=0.24, real=1.17 secs] 2018-11-10T18:23:33.661+0800: 1495276.782: [CMS-concurrent-reset-start] 2018-11-10T18:23:33.667+0800: 1495276.788: [CMS-concurrent-reset: 0.006/0.006 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
初始標記(CMS Initial Mark)
- 該階段進行可達性分析,標記GC ROOTS能直接關(guān)聯(lián)到的對象。該階段會暫停應(yīng)用。
- 2008820K – 當前老年代使用情況;
- (2510848K) – 老年代可用容量;
- 2038212K – 當前整個堆的使用情況;
- (4398336K) – 整個堆的容量;
- .0231086 secs] [Times: user=0.01 sys=0.00, real=0.03 secs] – 時間計量;
并發(fā)標記(CMS-concurrent-mark)
并發(fā)標記就需要標記出 GC ROOTS 關(guān)聯(lián)到的對象的引用對象有哪些。比如說 A -> B (A 引用 B,假設(shè) A 是 GC Roots 關(guān)聯(lián)到的對象),那么這個階段就是標記出 B 對象, A 對象會在初始標記中標記出來。
并發(fā)預(yù)清理(CMS-concurrent-preclean)
這個階段主要并發(fā)查找在做并發(fā)標記階段時從年輕代晉升到老年代的對象或老年代新分配的對象(大對象直接進入老年代)或被用戶線程更新的對象,來減少重新標記階段的工作量。
重新標記(CMS Final Remark)
由于在并發(fā)標記和并發(fā)預(yù)清理這個階段,用戶線程和GC 線程并發(fā),假如這個階段用戶線程產(chǎn)生了新的對象,總不能被 GC 掉吧。這個階段就是為了讓這些對象重新標記。該階段也會暫停應(yīng)用
- YG occupancy: 847369 K (1887488 K)]– 年輕代當前占用情況和容量;
- Rescan (parallel) , 0.0902177 secs – 這個階段在應(yīng)用停止的階段完成存活對象的標記工作;
- weak refs processing, 0.0514433 secs – 第一個子階段,隨著這個階段的進行處理弱引用;
- class unloading, 0.0256119 secs– 第二個子階段(that is unloading the unused classes, with the duration and timestamp of the phase);
- scrub symbol table, 0.0074695 secs– 最后一個子階段(that is cleaning up symbol and string tables which hold class-level metadata and internalized string respectively)
- 2008820K(2510848K)]– 在這個階段之后老年代占有的內(nèi)存大小和老年代的容量;
- 2856190K(4398336K)– 在這個階段之后整個堆的內(nèi)存大小和整個堆的容量;
- 0.1806988 secs – 這個階段的持續(xù)時間;
- [Times: user=0.68 sys=0.00, real=0.18 secs] – 同上;
并發(fā)清理(CMS-concurrent-sweep)
這個階段的目的就是移除那些不用的對象,回收他們占用的空間并且為將來使用。注意這個階段會產(chǎn)生新的垃圾,新的垃圾在此次GC無法清除,只能等到下次清理。這些垃圾有個專業(yè)名詞:浮動垃圾。
并發(fā)重置(CMS-concurrent-reset)
CMS清除內(nèi)部狀態(tài),為下次回收做準備。
注意:CMS雖然是老年代算法,但也是需要掃描新生代區(qū)域的。
CMS算法降級
cms存在著內(nèi)存碎片化問題:申請內(nèi)存時,雖然總內(nèi)存大于申請內(nèi)存,但是沒有連續(xù)內(nèi)存大于申請內(nèi)存,導(dǎo)致內(nèi)存申請失敗。CMS提供了機制(CMS GC降級到Full GC)來解決該問題。Full GC使用的算法是mark-sweep-compact(類似于Serial垃圾回收器),他的作用域在整個堆的對象,包括年輕代、老年代、持久代,但compaction是可選的。其中參數(shù)CMSFullGCsBeforeCompaction=N表示每隔N次真正的full GC才做一次壓縮(而不是每N次CMS GC就做一次壓縮,目前JVM里沒有這樣的參數(shù)),CMSFullGCsBeforeCompaction默認值是0,也就是每次full GC都會進行內(nèi)存壓縮。這個盡量使用默認值,不然內(nèi)存碎片化可能會更嚴重些。
那么配置的CMS GC啥時候會觸發(fā)Full gc呢?主要有下面幾種情況觸發(fā)Full Gc:
- 舊生代空間不足:java.lang.outOfMemoryError:java heap space;
- Perm空間滿:java.lang.outOfMemoryError:PermGen space;
- CMS GC時出現(xiàn)promotion failed(當進行 Young GC 時,有部分新生代代對象仍然可用,但是S0或S1放不下,因此需要放到老年代,但此時老年代空間無法容納這些對象) 和concurrent mode failure(當 CMS GC 正進行時,此時有新的對象要進行老年代,但是老年代空間不足造成的);
- 統(tǒng)計得到的minor GC晉升到舊生代的平均大小大于舊生代的剩余空間;
- 主動觸發(fā)Full GC(System.gc()、jmap等)。
如何識別是執(zhí)行的是CMS GC還是 Full GC呢?主要是根據(jù)GC log,CMS GC會在日志中標記出各個執(zhí)行階段,但是要是執(zhí)行Full GC只會顯示full次數(shù)加1。
CMS相關(guān)參數(shù)
-XX:CMSInitiatingOccupancyFraction=N 和-XX:+UseCMSInitiatingOccupancyOnly
這兩個設(shè)置一般配合使用, 目的在于降低CMS GC頻率或者增加頻率。
-XX:CMSInitiatingOccupancyFraction=N 是指設(shè)定CMS在對內(nèi)存占用率達到N%的時候開始進行CMS GC。
-XX:+UseCMSInitiatingOccupancyOnly 只是用設(shè)定的回收閾值(上面指定的N%),如果不指定,JVM僅在第一次使用設(shè)定值,后續(xù)則自動調(diào)整.
-XX:+CMSScavengeBeforeRemark
這個參數(shù)表示CMS GC前啟動一次ygc,目的在于減少old區(qū)域?qū)gc區(qū)域的引用,降低remark時的開銷,一般CMS的GC耗時80%都在remark階段
-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction=N
這兩個參數(shù)要配合使用,其中CMSFullGCsBeforeCompaction上面已經(jīng)講解過了。
CMS 的缺點
- 會產(chǎn)生空間碎片。CMS 垃圾回收器采用的基礎(chǔ)算法是 Mark-Sweep,沒有內(nèi)存整理的過程,所以經(jīng)過 CMS 收集的堆會產(chǎn)生空間碎片。
- 對CPU資源非常敏感。為了讓應(yīng)用程序不停頓,CMS 線程需要和應(yīng)用程序線程并發(fā)執(zhí)行,這樣就需要有更多的 CPU,同時會使得總吞吐量降低。
- CMS無法處理浮動垃圾,所以一般需要更大的堆空間。因為CMS 在標記階段應(yīng)用程序的線程還是在執(zhí)行的,那么就會有堆空間繼續(xù)分配的情況,為了保證在 CMS 回收完堆之前還有空間分配給正在運行的應(yīng)用程序,必須預(yù)留一部分空間。
以上就是淺析JVM的垃圾回收器的詳細內(nèi)容,更多關(guān)于jvm 垃圾回收器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java SpringCache+Redis緩存數(shù)據(jù)詳解
本篇文章主要介紹了淺談SpringCache與redis緩存數(shù)據(jù)的解決方案,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2021-10-10java并發(fā)編程專題(四)----淺談(JUC)Lock鎖
這篇文章主要介紹了java并發(fā)編程(JUC)Lock鎖的相關(guān)內(nèi)容,文中講解非常細致,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下2020-06-06java多線程CountDownLatch與線程池ThreadPoolExecutor/ExecutorService案
這篇文章主要介紹了java多線程CountDownLatch與線程池ThreadPoolExecutor/ExecutorService案例,2021-02-02SpringBoot校園綜合管理系統(tǒng)實現(xiàn)流程分步講解
這篇文章主要介紹了SpringBoot+Vue實現(xiàn)校園綜合管理系統(tǒng)流程分步講解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-09-09IDEA?code?template配置和參數(shù)方式
這篇文章主要介紹了IDEA?code?template配置和參數(shù)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教<BR>2024-01-01Java基于ServletContextListener實現(xiàn)UDP監(jiān)聽
這篇文章主要介紹了Java基于ServletContextListener實現(xiàn)UDP監(jiān)聽,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-12-12使用Spring boot 的profile功能實現(xiàn)多環(huán)境配置自動切換
這篇文章主要介紹了使用Spring boot 的profile功能實現(xiàn)多環(huán)境配置自動切換的相關(guān)知識,非常不錯,具有一定的參考借鑒價值 ,需要的朋友可以參考下2018-11-11SpringBoot各種事務(wù)操作實戰(zhàn)(自動回滾、手動回滾、部分回滾)
本文主要介紹了SpringBoot各種事務(wù)操作實戰(zhàn),包含自動回滾、手動回滾、部分回滾這三種,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-05-05Springboot項目中如何讓非Spring管理的類獲得一個注入的Bean
這篇文章主要介紹了Springboot項目中如何讓非Spring管理的類獲得一個注入的Bean問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12