JVM中的垃圾回收器使用及說明
一、垃圾回收器類型
如果說垃圾收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體 實(shí)現(xiàn)。
下圖展示了7種作用于不同分代的收集器,其中用于回收新生代的收集器 包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用于回收整個(gè)Java堆的G1收集器。不同收集器 之間的連線表示它們可以搭配使用。

1. Serial收集器(復(fù)制算法)
新生代單線程收集器,標(biāo)記和清理都是單線程,優(yōu)點(diǎn)是簡單高效;
2. ParNew收集器 (復(fù)制算法)
新生代并行收集器,實(shí)際上是Serial收集器的多線程 版本,在多核CPU環(huán)境下有著比Serial更好的表現(xiàn);
3. Parallel Scavenge收集器 (復(fù)制算法)
新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用戶線程時(shí)間/(用戶線程時(shí)間+GC線程時(shí)間),高吞吐量可以高效率的利用CPU時(shí)間,盡快完成程序的運(yùn)算任務(wù),適合后臺應(yīng)用等對交互相應(yīng)要求不高的場景;
4. Serial Old收集器 (標(biāo)記-整理算法)
老年代單線程收集器,Serial收集器的老年代版本;
5. Parallel Old收集器 (標(biāo)記-整理算法)
老年代并行收集器,吞吐量優(yōu)先, Parallel Scavenge收集器的老年代版本;
6. CMS(Concurrent Mark Sweep)收集器(標(biāo)記-清除算法)
老年代并行收集器,以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,具有高并發(fā)、低停頓的特點(diǎn),追求最短GC回收停頓時(shí)間。
CMS回收過程:
a. 初始標(biāo)記:僅標(biāo)記GC Roots 能直接關(guān)聯(lián)的對象,這個(gè)階段會(huì)導(dǎo)致 STW(stop the world)。用戶無法操作。
b. 并發(fā)標(biāo)記:從 GC Roots 直接關(guān)聯(lián)的對象進(jìn)行遍歷,不需要STW,可以與GC線程一起并發(fā)執(zhí)行。
c. 重新標(biāo)記:重新標(biāo)記期間,因用戶程序繼續(xù)運(yùn)行而導(dǎo)致的標(biāo)記產(chǎn)生變動(dòng)的那一部分對象的標(biāo)記記錄,比初始標(biāo)記時(shí)間長,但遠(yuǎn)比并發(fā)標(biāo)記時(shí)間短,也會(huì)導(dǎo)致 STW。
d. 并發(fā)清除:清除標(biāo)記為死亡的對象,釋放內(nèi)存空間,可以與用戶線程并發(fā)執(zhí)行。
7. G1(Garbage First)收集器 (標(biāo)記-整理算法)
Java堆并行收集器,G1收集器是 JDK1.7提供的一個(gè)新收集器,G1收集器基于“標(biāo)記-整理”算法實(shí)現(xiàn),也就是說不會(huì)產(chǎn)生內(nèi)存碎片。此外,G1收集器不同于之前的收集器的一個(gè)重要特點(diǎn)是:G1回收的范圍是整個(gè)Java堆(包括新生代,老年代),而前六種收集器回收的范圍僅限于新生代或老年代。
G1收集器可以精確控制停頓時(shí)間,讓使用者明確一個(gè)指定長度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集器上的時(shí)間不超過N毫秒,幾乎是實(shí)時(shí)java的垃圾回收器的特征了。
收集過程:避免全區(qū)域收集,將堆空間分成若干區(qū)域,這個(gè)區(qū)域邏輯上包含的新生代、老年代,并且不要求整個(gè)Eden、年輕代、老年代都是連續(xù)的。跟蹤這些區(qū)域垃圾堆積程度,維護(hù)一張優(yōu)先級列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收垃圾最多的區(qū)域,從而獲取更高的效率。
二、分代垃圾回收器工作流程
分代垃圾回收是一種內(nèi)存管理策略,是內(nèi)存管理策略層面的概念,不是具體的實(shí)現(xiàn)。上面介紹的7中垃圾回收器是具體的實(shí)現(xiàn)。例如:Serial Old收集器可以是分代垃圾回收策略中老年代回收的一個(gè)組成部分,是策略與實(shí)現(xiàn)的關(guān)系。
分代回收器有兩個(gè)分區(qū):老生代和新生代,新生代默認(rèn)的空間占比總空間的 1/3,老生代的默認(rèn)占比是 2/3。 新生代使用的是復(fù)制算法,新生代里有 3 個(gè)分區(qū):Eden、To Survivor、From Survivor,它們的默認(rèn)占比是 8:1:1,它的執(zhí)行流程如下:
1.把 Eden + From Survivor 存活的對象放入 To Survivor 區(qū);
2.清空 Eden 和 From Survivor 分區(qū);
3. From Survivor 和 To Survivor 分區(qū)交換,F(xiàn)rom Survivor 變 To Survivor,To Survivor 變 From Survivor。
每次在 From Survivor 到 To Survivor 移動(dòng)時(shí)都存活的對象,年齡就 +1,當(dāng)年齡到達(dá) 15(默認(rèn)配置是 15)時(shí),升級為老生代。大對象也會(huì)直接進(jìn)入老生代。 老生代當(dāng)空間占用到達(dá)某個(gè)值之后就會(huì)觸發(fā)全局垃圾收回,一般使用標(biāo)記整理的執(zhí)行算法。以上這些循環(huán)往復(fù)就構(gòu)成了整個(gè)分代垃圾回收的整體執(zhí)行流程。
三、java內(nèi)存分配與回收策率
對象的內(nèi)存分配通常是在 Java 堆上分配(隨著虛擬機(jī)優(yōu)化技術(shù)的誕生,某些場景下也會(huì)在棧上分配),對象主要分配在新生代的 Eden 區(qū), 如果啟動(dòng)了本地線程緩沖,將按照線程優(yōu)先在 TLAB 上分配。少數(shù)情況下也會(huì)直接在老年代上分配。總的來說分配規(guī)則不是百分百固定的,其細(xì)節(jié)取決于哪一種垃圾收集器組合以及虛擬機(jī)相關(guān)參數(shù)有關(guān),但是虛擬機(jī)對于內(nèi)存的分配還是會(huì)遵循以下幾種「普世」規(guī)則:
1. 對象優(yōu)先在 Eden 區(qū)分配
多數(shù)情況,對象都在新生代 Eden 區(qū)分配。當(dāng) Eden 區(qū)分配沒有足夠的空間進(jìn)行分配時(shí),虛擬機(jī)將會(huì)發(fā)起一次 Minor GC。
如果本次 GC 后還是沒有足夠的空 間,則將啟用分配擔(dān)保機(jī)制在老年代中分配內(nèi)存。
Minor GC 是指發(fā)生在新生代的 GC,因?yàn)?Java 對象大多都是朝生夕死,所有 Minor GC 非常頻繁,一般回收速度也非常快; Major GC/Full GC 是指發(fā)生在老年代的 GC,出現(xiàn)了 Major GC 通常會(huì)伴隨至少一次 Minor GC。
Major GC 的速度通常會(huì)比 Minor GC 慢 10 倍以上。
2.大對象直接進(jìn)入老年代
所謂大對象是指需要大量連續(xù)內(nèi)存空間的對象,頻繁出現(xiàn)大對象是致命的,會(huì)導(dǎo)致在內(nèi)存還有不少空間的情況下提前觸發(fā) GC 以獲取足夠的連續(xù)空間來安置新對 象。
前面我們介紹過新生代使用的是標(biāo)記-清除算法來處理垃圾回收的,如果大對象直接在新生代分配就會(huì)導(dǎo)致 Eden 區(qū)和兩個(gè) Survivor 區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。因此對于大對象都會(huì)直接在老年代進(jìn)行分配。
3.長期存活對象將進(jìn)入老年代
虛擬機(jī)采用分代收集的思想來管理內(nèi)存,那么內(nèi)存回收時(shí)就必須判斷哪些對象應(yīng)該放在新生代,哪些對象應(yīng)該放在老年代。因此虛擬機(jī)給每個(gè)對象定義了一個(gè)對象年齡的計(jì)數(shù)器,如果對象在 Eden 區(qū)出生,并且能夠被 Survivor 容納,將被移動(dòng)到 Survivor 空間中,這時(shí)設(shè)置對象年齡為 1。對象在 Survivor 區(qū)中每「熬 過」一次 Minor GC 年齡就加 1,當(dāng)年齡達(dá)到一定程度(默認(rèn) 15) 就會(huì)被晉升到老年代。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中實(shí)現(xiàn)接口與繼承的區(qū)別及說明
這篇文章主要介紹了Java中實(shí)現(xiàn)接口與繼承的區(qū)別及說明,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
如何使用Java調(diào)用Linux系統(tǒng)命令
這篇文章主要介紹了如何使用Java調(diào)用Linux系統(tǒng)命令,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
IDEA使用MyBatisCodeHelperPro來generator代碼的詳細(xì)教程
這篇文章主要介紹了IDEA使用MyBatisCodeHelperPro來generator代碼的詳細(xì)教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
java 日志的數(shù)據(jù)脫敏的實(shí)現(xiàn)方法
今日給大家介紹一下java 日志的數(shù)據(jù)脫敏的實(shí)現(xiàn)方法,可以更好的保護(hù)數(shù)據(jù)的安全,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
實(shí)例分析Java Class的文件結(jié)構(gòu)
今天把之前在Evernote中的筆記重新整理了一下,發(fā)上來供對java class 文件結(jié)構(gòu)的有興趣的同學(xué)參考一下2013-04-04
java實(shí)現(xiàn)獲取安卓設(shè)備里已安裝的軟件包
本文給大家介紹的是如何獲取設(shè)備中已經(jīng)安裝的應(yīng)用軟件包的代碼,其核心方法原理很簡單,我們通過Android中提供的PackageManager類,來獲取手機(jī)中安裝的應(yīng)用程序信息2015-10-10
詳解Springboot @Cacheable 注解(指定緩存位置)
這篇文章主要介紹了詳解Springboot @Cacheable 注解(指定緩存位置),使用? @Cacheable ?注解就可以將運(yùn)行結(jié)果緩存,以后查詢相同的數(shù)據(jù),直接從緩存中取,不需要調(diào)用方法,需要的朋友可以參考下2023-09-09

