新手入門Jvm--Jvm垃圾回收
1. Jvm垃圾回收
Java虛擬機(jī)主要分為五大模塊:類裝載器子系統(tǒng)、運(yùn)行時數(shù)據(jù)區(qū)、執(zhí)行引擎、本地方法接口和垃圾收集模塊。其中垃圾收集模塊在Java虛擬機(jī)規(guī)范中并沒有要求Java虛擬機(jī)垃圾收集,但是在沒有發(fā)明無限的內(nèi)存之前,大多數(shù)JVM實現(xiàn)都是有垃圾收集的。
Java堆是內(nèi)存管理中最大的一塊,所有的線程共享這一塊內(nèi)容,同時該部分也是垃圾收集器的主要區(qū)域。
虛擬機(jī)的垃圾回收機(jī)制是完善的,動態(tài)內(nèi)存分配和回收是比較成熟的,在內(nèi)存管理機(jī)制中,大部分都不需要我們考慮內(nèi)存回收,只有Java堆和方法區(qū)需要我們考慮處理內(nèi)存問題。一般的對于內(nèi)存回收首先就是判斷某一個部分是生存還是死亡,主要是通過下面二種算法:
其一是引用計數(shù)算法,本算法實現(xiàn)簡單,判定的效率也是比較高的,很多的軟件都使用了該算法,但是主流的Java并沒有選擇該算法,核心的問題是該算法難以處理對象之間相互調(diào)用的問題。
其二是稱可達(dá)性分析算法,該算法核心思想是依靠判斷對象是否存活來實現(xiàn)的,本算法是通過一系列的GC ROOTS的對象作為起始點,采用搜索的算法遍歷引用鏈,如果搜索過程中沒有發(fā)現(xiàn)該節(jié)點,則認(rèn)為該節(jié)點是不可達(dá)的,即可回收的,在Java里面,一般可以使用該算法處理問題。
2. 作用域
- JVM 堆
- 年輕代
- 老年代
- 元空間
3. 分類
當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法,這種算法沒有什么新的思想,只是根據(jù)對象存活周期的不同將內(nèi)存分為幾塊。一般將java堆分為新生代和老年代,這樣我們就可以根據(jù)各個年代的特點選擇合適的垃圾收集算法。
4. 垃圾回收算法
4.1 標(biāo)記-復(fù)制算法
為了解決效率問題,“復(fù)制”收集算法出現(xiàn)了。它可以將內(nèi)存分為大小相同的兩塊,每次使用其中的一塊。當(dāng)這一塊的內(nèi)存使用完后,就將還存活的對象復(fù)制到另一塊去,然后再把使用的空間一次清理掉。這樣就使每次的內(nèi)存回收都是對內(nèi)存區(qū)間的一半進(jìn)行回收
4.2 標(biāo)記-清除算法
算法分為“標(biāo)記”和“清除”階段:標(biāo)記存活的對象, 統(tǒng)一回收所有未被標(biāo)記的對象(一般選擇這種);也可以反過來,標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象 。它是最基礎(chǔ)的收集算法,比較簡單,但是會帶來兩個明顯的問題:
- 效率問題 (如果需要標(biāo)記的對象太多,效率不高)
- 空間問題(標(biāo)記清除后會產(chǎn)生大量不連續(xù)的碎片)
4.3 標(biāo)記-整理算法
根據(jù)老年代的特點特出的一種標(biāo)記算法,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象回收,而是讓所有存活的對象向一端移動,然后直接清理掉端邊界以外的內(nèi)存。
5. 垃圾收集器
5.1 Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC)
Serial(串行)收集器是最基本、歷史最悠久的垃圾收集器了。大家看名字就知道這個收集器是一個單線程收集器了。它的 “單線程” 的意義不僅僅意味著它只會使用一條垃圾收集線程去完成垃圾收集工作,更重要的是它在進(jìn)行垃圾收集工作的時候必須暫停其他所有的工作線程( “Stop The World” ),直到它收集結(jié)束。
新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法。
5.2 Parallel Scavenge收集器(-XX:+UseParallelGC(年輕代),-XX:+UseParallelOldGC(老年代))
Parallel收集器其實就是Serial收集器的多線程版本,除了使用多線程進(jìn)行垃圾收集外,其余行為(控制參數(shù)、收集算法、回收策略等等)和Serial收集器類似。默認(rèn)的收集線程數(shù)跟cpu核數(shù)相同,當(dāng)然也可以用參數(shù)(-XX:ParallelGCThreads)指定收集線程數(shù),但是一般不推薦修改。
Parallel Scavenge收集器關(guān)注點是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的關(guān)注點更多的是用戶線程的停頓時間(提高用戶體驗)。所謂吞吐量就是CPU中用于運(yùn)行用戶代碼的時間與CPU總消耗時間的比值。 Parallel Scavenge收集器提供了很多參數(shù)供用戶找到最合適的停頓時間或最大吞吐量,如果對于收集器運(yùn)作不太了解的話,可以選擇把內(nèi)存管理優(yōu)化交給虛擬機(jī)去完成也是一個不錯的選擇。新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多線程和“標(biāo)記-整理”算法。在注重吞吐量以及CPU資源的場合,都可以優(yōu)先考慮 Parallel Scavenge收集器和Parallel Old收集器(JDK8默認(rèn)的新生代和老年代收集器)。
5.3 ParNew收集器(-XX:+UseParNewGC)
ParNew收集器其實跟Parallel收集器很類似,區(qū)別主要在于它可以和CMS收集器配合使用。
新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法。
它是許多運(yùn)行在Server模式下的虛擬機(jī)的首要選擇,除了Serial收集器外,只有它能與CMS收集器(真正意義上的并發(fā)收集器,后面會介紹到)配合工作。
5.4 CMS收集器(-XX:+UseConcMarkSweepGC(old))
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器。它非常符合在注重用戶體驗的應(yīng)用上使用,它是HotSpot虛擬機(jī)第一款真正意義上的并發(fā)收集器,它第一次實現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時工作。
從名字中的Mark Sweep這兩個詞可以看出,CMS收集器是一種 “標(biāo)記-清除”算法實現(xiàn)的,它的運(yùn)作過程相比于前面幾種垃圾收集器來說更加復(fù)雜一些。整個過程分為四個步驟:
- 初始標(biāo)記: 暫停所有的其他線程(STW),并記錄下gc roots直接能引用的對象,速度很快。
- 并發(fā)標(biāo)記: 并發(fā)標(biāo)記階段就是從GC Roots的直接關(guān)聯(lián)對象開始遍歷整個對象圖的過程, 這個過程耗時較長但是不需要停頓用戶線程, 可以與垃圾收集線程一起并發(fā)運(yùn)行。因為用戶程序繼續(xù)運(yùn)行,可能會有導(dǎo)致已經(jīng)標(biāo)記過的對象狀態(tài)發(fā)生改變。
- 重新標(biāo)記: 重新標(biāo)記階段就是為了修正并發(fā)標(biāo)記期間因為用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,這個階段的停頓時間一般會比初始標(biāo)記階段的時間稍長,遠(yuǎn)遠(yuǎn)比并發(fā)標(biāo)記階段時間短。主要用到三色標(biāo)記里的增量更新算法(見下面詳解)做重新標(biāo)記。
- 并發(fā)清理: 開啟用戶線程,同時GC線程開始對未標(biāo)記的區(qū)域做清掃。這個階段如果有新增對象會被標(biāo)記為黑色不做任何處理(見下面三色標(biāo)記算法詳解)。
- 并發(fā)重置:重置本次GC過程中的標(biāo)記數(shù)據(jù)。
從它的名字就可以看出它是一款優(yōu)秀的垃圾收集器,主要優(yōu)點:并發(fā)收集、低停頓。但是它有下面幾個
明顯的缺點:
- 對CPU資源敏感(會和服務(wù)搶資源);
- 無法處理浮動垃圾(在并發(fā)標(biāo)記和并發(fā)清理階段又產(chǎn)生垃圾,這種浮動垃圾只能等到下一次gc再清理了);
它使用的回收算法-“標(biāo)記-清除”算法會導(dǎo)致收集結(jié)束時會有大量空間碎片產(chǎn)生,當(dāng)然通過參數(shù)-XX:+UseCMSCompactAtFullCollection可以讓jvm在執(zhí)行完標(biāo)記清除后再做整理執(zhí)行過程中的不確定性,會存在上一次垃圾回收還沒執(zhí)行完,然后垃圾回收又被觸發(fā)的情況,特別是在并發(fā)標(biāo)記和并發(fā)清理階段會出現(xiàn),一邊回收,系統(tǒng)一邊運(yùn)行,也許沒回收完就再次觸發(fā)full gc,也就是"concurrent mode failure",此時會進(jìn)入stop the world,用serial old垃圾收集器來回收
5.5 CMS的相關(guān)核心參數(shù)
-XX:+UseConcMarkSweepGC:啟用cms
-XX:ConcGCThreads:并發(fā)的GC線程數(shù)
-XX:+UseCMSCompactAtFullCollection:FullGC之后做壓縮整理(減少碎片)
-XX:CMSFullGCsBeforeCompaction:多少次FullGC之后壓縮一次,默認(rèn)是0,代表每次FullGC后都會壓縮一次
-XX:CMSInitiatingOccupancyFraction: 當(dāng)老年代使用達(dá)到該比例時會觸發(fā)FullGC(默認(rèn)是92,這是百分比)
-XX:+UseCMSInitiatingOccupancyOnly:只使用設(shè)定的回收閾值(-XX:CMSInitiatingOccupancyFraction設(shè)定的值),如果不指定,JVM僅在第一次使用設(shè)定值,后續(xù)則會自動調(diào)整
-XX:+CMSScavengeBeforeRemark:在CMS GC前啟動一次minor gc,降低CMS GC標(biāo)記階段(也會對年輕代一起做標(biāo)記,如果在minor gc就干掉了很多對垃圾對象,標(biāo)記階段就會減少一些標(biāo)記時間)時的開銷,一般CMS的GC耗時 80%都在標(biāo)記階段
-XX:+CMSParallellnitialMarkEnabled:表示在初始標(biāo)記的時候多線程執(zhí)行,縮短STW
-XX:+CMSParallelRemarkEnabled:在重新標(biāo)記的時候多線程執(zhí)行,縮短STW;
6. 垃圾收集底層算法實現(xiàn)
- 三色標(biāo)記
在并發(fā)標(biāo)記的過程中,因為標(biāo)記期間應(yīng)用線程還在繼續(xù)跑,對象間的引用可能發(fā)生變化,多標(biāo)和漏標(biāo)的情況就有可能發(fā)生。
這里我們引入“三色標(biāo)記”來給大家解釋下,把Gcroots可達(dá)性分析遍歷對象過程中遇到的對象, 按照“是否訪問過”這個條件標(biāo)記成以下三種顏色:
- 黑色: 表示對象已經(jīng)被垃圾收集器訪問過, 且這個對象的所有引用都已經(jīng)掃描過。 黑色的對象代表已經(jīng)掃描過, 它是安全存活的, 如果有其他對象引用指向了黑色對象, 無須重新掃描一遍。 黑色對象不可能直接(不經(jīng)過灰色對象) 指向某個白色對象。
- 灰色: 表示對象已經(jīng)被垃圾收集器訪問過, 但這個對象上至少存在一個引用還沒有被掃描過。
- 白色: 表示對象尚未被垃圾收集器訪問過。 顯然在可達(dá)性分析剛剛開始的階段, 所有的對象都是白色的, 若在分析結(jié)束的階段, 仍然是白色的對象, 即代表不可達(dá)。
7 .總結(jié)
Jvm優(yōu)化主要是防止fullgc,縮短STW時間,杜絕OOM出現(xiàn),而實現(xiàn)這一手段主要是依賴?yán)厥諜C(jī)制,具體來說就是垃圾回收器,而垃圾回收器又分了好多種,單線程,并發(fā),回收算法的差異性等等,所以要做到深度優(yōu)化,必須理解其底層機(jī)制。
相關(guān)文章
MyBatis-Plus中實現(xiàn)自定義復(fù)雜排序邏輯的詳細(xì)步驟
這篇文章主要介紹了MyBatis-Plus中實現(xiàn)自定義復(fù)雜排序邏輯,通過使用MyBatis-Plus的QueryWrapper和SQL原始片段,我們可以靈活地實現(xiàn)復(fù)雜的數(shù)據(jù)排序邏輯,這種方法尤其適用于需要對數(shù)據(jù)進(jìn)行特定規(guī)則排序的場景,需要的朋友可以參考下2024-07-07springboot如何通過SSH連接遠(yuǎn)程服務(wù)器
這篇文章主要介紹了springboot如何通過SSH連接遠(yuǎn)程服務(wù)器問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07SpringBoot JavaMailSender發(fā)送郵件功能
這篇文章主要為大家詳細(xì)介紹了SpringBoot JavaMailSender發(fā)送郵件功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04SpringBoot?@InitBinder注解綁定請求參數(shù)的過程詳解
這篇文章主要介紹了SpringBoot?@InitBinder注解綁定請求參數(shù),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04Java程序執(zhí)行過程及內(nèi)存機(jī)制詳解
本講將介紹Java代碼是如何一步步運(yùn)行起來的,還會介紹Java程序所占用的內(nèi)存是被如何管理的:堆、棧和方法區(qū)都各自負(fù)責(zé)存儲哪些內(nèi)容,感興趣的朋友跟隨小編一起看看吧2020-12-12在SpringBoot: SpringBoot里面創(chuàng)建導(dǎo)出Excel的接口教程
這篇文章主要介紹了在SpringBoot: SpringBoot里面創(chuàng)建導(dǎo)出Excel的接口教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10如何將JSP/Servlet項目轉(zhuǎn)換為Spring Boot項目
這篇文章主要介紹了如何將JSP/Servlet項目轉(zhuǎn)換為Spring Boot項目,幫助大家更好的利用springboot進(jìn)行網(wǎng)絡(luò)編程,感興趣的朋友可以了解下2020-10-10