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

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

