溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

發(fā)布時(shí)間:2020-07-14 22:48:52 來源:網(wǎng)絡(luò) 閱讀:337 作者:架構(gòu)師追風(fēng) 欄目:編程語言

先來看看5種JVM垃圾收集器特點(diǎn)

一、常見垃圾收集器

現(xiàn)在常見的垃圾收集器有如下幾種:
新生代收集器:
Serial
ParNew
Parallel Scavenge
老年代收集器
Serial Old
CMS
Parallel Old
堆內(nèi)存垃圾收集器:G1
每種垃圾收集器之間有連線,表示他們可以搭配使用。
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

二、新生代垃圾收集器

(1)Serial 收集器
Serial 是一款用于新生代的單線程收集器,采用復(fù)制算法進(jìn)行垃圾收集。Serial 進(jìn)行垃圾收集時(shí),不僅只用一條線程執(zhí)行垃圾收集工作,它在收集的同時(shí),所有的用戶線程必須暫停(Stop The World)。
就比如媽媽在家打掃衛(wèi)生的時(shí)候,肯定不會(huì)邊打掃邊讓兒子往地上亂扔紙屑,否則一邊制造垃圾,一遍清理垃圾,這活啥時(shí)候也干不完。
如下是 Serial 收集器和 Serial Old 收集器結(jié)合進(jìn)行垃圾收集的示意圖,當(dāng)用戶線程都執(zhí)行到安全點(diǎn)時(shí),所有線程暫停執(zhí)行,Serial 收集器以單線程,采用復(fù)制算法進(jìn)行垃圾收集工作,收集完之后,用戶線程繼續(xù)開始執(zhí)行。
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

適用場(chǎng)景:Client 模式(桌面應(yīng)用);單核服務(wù)器。
可以用 -XX:+UserSerialGC 來選擇 Serial 作為新生代收集器。
(2)ParNew 收集器
ParNew 就是一個(gè) Serial 的多線程版本,其它與Serial并無區(qū)別。ParNew 在單核 CPU 環(huán)境并不會(huì)比 Serial 收集器達(dá)到更好的效果,它默認(rèn)開啟的收集線程數(shù)和 CPU 數(shù)量一致,可以通過 -XX:ParallelGCThreads 來設(shè)置垃圾收集的線程數(shù)。
如下是 ParNew 收集器和 Serial Old 收集器結(jié)合進(jìn)行垃圾收集的示意圖,當(dāng)用戶線程都執(zhí)行到安全點(diǎn)時(shí),所有線程暫停執(zhí)行,ParNew 收集器以多線程,采用復(fù)制算法進(jìn)行垃圾收集工作,收集完之后,用戶線程繼續(xù)開始執(zhí)行。
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

適用場(chǎng)景:多核服務(wù)器;與 CMS 收集器搭配使用。當(dāng)使用 -XX:+UserConcMarkSweepGC 來選擇 CMS 作為老年代收集器時(shí),新生代收集器默認(rèn)就是 ParNew,也可以用 -XX:+UseParNewGC 來指定使用 ParNew 作為新生代收集器。
(3)Parallel Scavenge 收集器
Parallel Scavenge 也是一款用于新生代的多線程收集器,與 ParNew 的不同之處是ParNew 的目標(biāo)是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,Parallel Scavenge 的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量。
吞吐量就是 CPU 執(zhí)行用戶線程的的時(shí)間與 CPU 執(zhí)行總時(shí)間的比值【吞吐量 = 運(yùn)行用戶代代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間)】,比如虛擬機(jī)一共運(yùn)行了 100 分鐘,其中垃圾收集花費(fèi)了 1 分鐘,那吞吐量就是 99% 。比如下面兩個(gè)場(chǎng)景,垃圾收集器每 100 秒收集一次,每次停頓 10 秒,和垃圾收集器每 50 秒收集一次,每次停頓時(shí)間 7 秒,雖然后者每次停頓時(shí)間變短了,但是總體吞吐量變低了,CPU 總體利用率變低了。
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

可以通過 -XX:MaxGCPauseMillis 來設(shè)置收集器盡可能在多長(zhǎng)時(shí)間內(nèi)完成內(nèi)存回收,可以通過 -XX:GCTimeRatio 來精確控制吞吐量。
如下是 Parallel 收集器和 Parallel Old 收集器結(jié)合進(jìn)行垃圾收集的示意圖,在新生代,當(dāng)用戶線程都執(zhí)行到安全點(diǎn)時(shí),所有線程暫停執(zhí)行,ParNew 收集器以多線程,采用復(fù)制算法進(jìn)行垃圾收集工作,收集完之后,用戶線程繼續(xù)開始執(zhí)行;在老年代,當(dāng)用戶線程都執(zhí)行到安全點(diǎn)時(shí),所有線程暫停執(zhí)行,Parallel Old 收集器以多線程,采用標(biāo)記整理算法進(jìn)行垃圾收集工作。
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

適用場(chǎng)景:注重吞吐量,高效利用 CPU,需要高效運(yùn)算且不需要太多交互。
可以使用 -XX:+UseParallelGC 來選擇 Parallel Scavenge 作為新生代收集器,jdk7、jdk8 默認(rèn)使用 Parallel Scavenge 作為新生代收集器。

三、老年代垃圾收集器

(1)Serial Old 收集器
Serial Old 收集器是 Serial 的老年代版本,同樣是一個(gè)單線程收集器,采用標(biāo)記-整理算法。
如下圖是 Serial 收集器和 Serial Old 收集器結(jié)合進(jìn)行垃圾收集的示意圖:
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

適用場(chǎng)景:Client 模式(桌面應(yīng)用);單核服務(wù)器;與 Parallel Scavenge 收集器搭配;作為 CMS 收集器的后備預(yù)案。
(2)CMS(Concurrent Mark Sweep) 收集器
CMS 收集器是一種以最短回收停頓時(shí)間為目標(biāo)的收集器,以 “ 最短用戶線程停頓時(shí)間 ” 著稱。整個(gè)垃圾收集過程分為 4 個(gè)步驟:
① 初始標(biāo)記:標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對(duì)象,速度較快。
② 并發(fā)標(biāo)記:進(jìn)行 GC Roots Tracing,標(biāo)記出全部的垃圾對(duì)象,耗時(shí)較長(zhǎng)。
③ 重新標(biāo)記:修正并發(fā)標(biāo)記階段引用戶程序繼續(xù)運(yùn)行而導(dǎo)致變化的對(duì)象的標(biāo)記記錄,耗時(shí)較短。
④ 并發(fā)清除:用標(biāo)記-清除算法清除垃圾對(duì)象,耗時(shí)較長(zhǎng)。
整個(gè)過程耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除都是和用戶線程一起工作,所以從總體上來說,CMS 收集器垃圾收集可以看做是和用戶線程并發(fā)執(zhí)行的。
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因
CMS 收集器也存在一些缺點(diǎn):
對(duì) CPU 資源敏感:默認(rèn)分配的垃圾收集線程數(shù)為(CPU 數(shù)+3)/4,隨著 CPU 數(shù)量下降,占用 CPU 資源越多,吞吐量越小
無法處理浮動(dòng)垃圾:在并發(fā)清理階段,由于用戶線程還在運(yùn)行,還會(huì)不斷產(chǎn)生新的垃圾,CMS 收集器無法在當(dāng)次收集中清除這部分垃圾。同時(shí)由于在垃圾收集階段用戶線程也在并發(fā)執(zhí)行,CMS 收集器不能像其他收集器那樣等老年代被填滿時(shí)再進(jìn)行收集,需要預(yù)留一部分空間提供用戶線程運(yùn)行使用。當(dāng) CMS 運(yùn)行時(shí),預(yù)留的內(nèi)存空間無法滿足用戶線程的需要,就會(huì)出現(xiàn) “ Concurrent Mode Failure ”的錯(cuò)誤,這時(shí)將會(huì)啟動(dòng)后備預(yù)案,臨時(shí)用 Serial Old 來重新進(jìn)行老年代的垃圾收集。
因?yàn)?CMS 是基于標(biāo)記-清除算法,所以垃圾回收后會(huì)產(chǎn)生空間碎片,可以通過 -XX:UserCMSCompactAtFullCollection 開啟碎片整理(默認(rèn)開啟),在 CMS 進(jìn)行 Full GC 之前,會(huì)進(jìn)行內(nèi)存碎片的整理。還可以用 -XX:CMSFullGCsBeforeCompaction 設(shè)置執(zhí)行多少次不壓縮(不進(jìn)行碎片整理)的 Full GC 之后,跟著來一次帶壓縮(碎片整理)的 Full GC。
適用場(chǎng)景:重視服務(wù)器響應(yīng)速度,要求系統(tǒng)停頓時(shí)間最短。可以使用 -XX:+UserConMarkSweepGC 來選擇 CMS 作為老年代收集器。
(3)Parallel Old 收集器
Parallel Old 收集器是 Parallel Scavenge 的老年代版本,是一個(gè)多線程收集器,采用標(biāo)記-整理算法??梢耘c Parallel Scavenge 收集器搭配,可以充分利用多核 CPU 的計(jì)算能力。
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

適用場(chǎng)景:與Parallel Scavenge 收集器搭配使用;注重吞吐量。jdk7、jdk8 默認(rèn)使用該收集器作為老年代收集器,使用 -XX:+UseParallelOldGC 來指定使用 Paralle Old 收集器。

四、新生代和老年代垃圾收集器

G1 收集器
G1 收集器是 jdk1.7 才正式引用的商用收集器,現(xiàn)在已經(jīng)成為 jdk9 默認(rèn)的收集器。前面幾款收集器收集的范圍都是新生代或者老年代,G1 進(jìn)行垃圾收集的范圍是整個(gè)堆內(nèi)存,它采用 “ 化整為零 ” 的思路,把整個(gè)堆內(nèi)存劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),在 G1 收集器中還保留著新生代和老年代的概念,它們分別都是一部分 Region,如下圖:
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

每一個(gè)方塊就是一個(gè)區(qū)域,每個(gè)區(qū)域可能是 Eden、Survivor、老年代,每種區(qū)域的數(shù)量也不一定。JVM 啟動(dòng)時(shí)會(huì)自動(dòng)設(shè)置每個(gè)區(qū)域的大?。?M ~ 32M,必須是 2 的次冪),最多可以設(shè)置 2048 個(gè)區(qū)域(即支持的最大堆內(nèi)存為 32M*2048 = 64G),假如設(shè)置 -Xmx8g -Xms8g,則每個(gè)區(qū)域大小為 8g/2048=4M。
為了在 GC Roots Tracing 的時(shí)候避免掃描全堆,在每個(gè) Region 中,都有一個(gè) Remembered Set 來實(shí)時(shí)記錄該區(qū)域內(nèi)的引用類型數(shù)據(jù)與其他區(qū)域數(shù)據(jù)的引用關(guān)系(在前面的幾款分代收集中,新生代、老年代中也有一個(gè) Remembered Set 來實(shí)時(shí)記錄與其他區(qū)域的引用關(guān)系),在標(biāo)記時(shí)直接參考這些引用關(guān)系就可以知道這些對(duì)象是否應(yīng)該被清除,而不用掃描全堆的數(shù)據(jù)。
G1 收集器可以 “ 建立可預(yù)測(cè)的停頓時(shí)間模型 ”,它維護(hù)了一個(gè)列表用于記錄每個(gè) Region 回收的價(jià)值大?。ɑ厥蘸螳@得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值),這樣可以保證 G1 收集器在有限的時(shí)間內(nèi)可以獲得最大的回收效率。
如下圖所示,G1 收集器收集器收集過程有初始標(biāo)記、并發(fā)標(biāo)記、最終標(biāo)記、篩選回收,和 CMS 收集器前幾步的收集過程很相似:
5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

① 初始標(biāo)記:標(biāo)記出 GC Roots 直接關(guān)聯(lián)的對(duì)象,這個(gè)階段速度較快,需要停止用戶線程,單線程執(zhí)行。
② 并發(fā)標(biāo)記:從 GC Root 開始對(duì)堆中的對(duì)象進(jìn)行可達(dá)新分析,找出存活對(duì)象,這個(gè)階段耗時(shí)較長(zhǎng),但可以和用戶線程并發(fā)執(zhí)行。
③ 最終標(biāo)記:修正在并發(fā)標(biāo)記階段引用戶程序執(zhí)行而產(chǎn)生變動(dòng)的標(biāo)記記錄。
④ 篩選回收:篩選回收階段會(huì)對(duì)各個(gè) Region 的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的 GC 停頓時(shí)間來指定回收計(jì)劃(用最少的時(shí)間來回收包含垃圾最多的區(qū)域,這就是 Garbage First 的由來——第一時(shí)間清理垃圾最多的區(qū)塊),這里為了提高回收效率,并沒有采用和用戶線程并發(fā)執(zhí)行的方式,而是停頓用戶線程。
適用場(chǎng)景:要求盡可能可控 GC 停頓時(shí)間;內(nèi)存占用較大的應(yīng)用??梢杂?-XX:+UseG1GC 使用 G1 收集器,jdk9 默認(rèn)使用 G1 收集器。

五、JVM垃圾收集器總結(jié)

本文主要介紹了JVM中的垃圾回收器,主要包括串行回收器、并行回收器以及CMS回收器、G1回收器。他們各自都有優(yōu)缺點(diǎn),通常來說你需要根據(jù)你的業(yè)務(wù),進(jìn)行基于垃圾回收器的性能測(cè)試,然后再做選擇。下面給出配置回收器時(shí),經(jīng)常使用的參數(shù):

-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:+UseParNewGC:在新生代使用并行收集器
-XX:+UseParallelGC :新生代使用并行回收收集器,更加關(guān)注吞吐量
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:ParallelGCThreads:設(shè)置用于垃圾回收的線程數(shù)
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:ParallelCMSThreads:設(shè)定CMS的線程數(shù)量
-XX:+UseG1GC:?jiǎn)⒂肎1垃圾回收器

5種JVM垃圾收集器特點(diǎn)和8種JVM內(nèi)存溢出原因

接下來看看8種JVM內(nèi)存溢出原因

一. Java 堆空間

發(fā)生頻率:5顆星
造成原因
無法在 Java 堆中分配對(duì)象
吞吐量增加
應(yīng)用程序無意中保存了對(duì)象引用,對(duì)象無法被 GC 回收
應(yīng)用程序過度使用 finalizer。finalizer 對(duì)象不能被 GC 立刻回收。finalizer 由結(jié)束隊(duì)列服務(wù)的守護(hù)線程調(diào)用,有時(shí) finalizer 線程的處理能力無法跟上結(jié)束隊(duì)列的增長(zhǎng)
解決方案
使用 -Xmx 增加堆大小
修復(fù)應(yīng)用程序中的內(nèi)存泄漏

二. GC 開銷超過限制

發(fā)生頻率:5顆星
造成原因
Java 進(jìn)程98%的時(shí)間在進(jìn)行垃圾回收,恢復(fù)了不到2%的堆空間,最后連續(xù)5個(gè)(編譯時(shí)常量)垃圾回收一直如此。
解決方案
使用 -Xmx 增加堆大小
使用 -XX:-UseGCOverheadLimit 取消 GC 開銷限制
修復(fù)應(yīng)用程序中的內(nèi)存泄漏

三. 請(qǐng)求的數(shù)組大小超過虛擬機(jī)限制

發(fā)生頻率:2顆星
造成原因
應(yīng)用程序試圖分配一個(gè)超過堆大小的數(shù)組
解決方案
使用 -Xmx 增加堆大小
修復(fù)應(yīng)用程序中分配巨大數(shù)組的 bug

四. Perm gen 空間

發(fā)生頻率:3顆星
造成原因
Perm gen 空間包含:
類的名字、字段、方法
與類相關(guān)的對(duì)象數(shù)組和類型數(shù)組
JIT 編譯器優(yōu)化
當(dāng) Perm gen 空間用盡時(shí),將拋出異常。
解決方案
使用 -XX: MaxPermSize 增加 Permgen 大小
不重啟應(yīng)用部署應(yīng)用程序可能會(huì)導(dǎo)致此問題。重啟 JVM 解決

五. Metaspace

發(fā)生頻率:3顆星
造成原因
從 Java 8 開始 Perm gen 改成了 Metaspace,在本機(jī)內(nèi)存中分配 class 元數(shù)據(jù)(稱為 metaspace)。如果 metaspace 耗盡,則拋出異常
解決方案
通過命令行設(shè)置 -XX: MaxMetaSpaceSize 增加 metaspace 大小
取消 -XX: maxmetsspacedize
減小 Java 堆大小,為 MetaSpace 提供更多的可用空間
為服務(wù)器分配更多的內(nèi)存
可能是應(yīng)用程序 bug,修復(fù) bug

六. 無法新建本機(jī)線程

發(fā)生頻率:5顆星
造成原因
內(nèi)存不足,無法創(chuàng)建新線程。由于線程在本機(jī)內(nèi)存中創(chuàng)建,報(bào)告這個(gè)錯(cuò)誤表明本機(jī)內(nèi)存空間不足
解決方案
為機(jī)器分配更多的內(nèi)存
減少 Java 堆空間
修復(fù)應(yīng)用程序中的線程泄漏。
增加操作系統(tǒng)級(jí)別的限制
ulimit -a
用戶進(jìn)程數(shù)增大 (-u) 1800
使用 -Xss 減小線程堆棧大小

七. 殺死進(jìn)程或子進(jìn)程

發(fā)生頻率:1顆星
造成原因
內(nèi)核任務(wù):內(nèi)存不足結(jié)束器,在可用內(nèi)存極低的情況下會(huì)殺死進(jìn)程
解決方案
將進(jìn)程遷移到不同的機(jī)器上
給機(jī)器增加更多內(nèi)存
與其他 OOM 錯(cuò)誤不同,這是由操作系統(tǒng)而非 JVM 觸發(fā)的。

八. 發(fā)生 stack_trace_with_native_method

發(fā)生頻率:1顆星
造成原因
本機(jī)方法(native method)分配失敗
打印的堆棧跟蹤信息,最頂層的幀是本機(jī)方法
解決方案
使用操作系統(tǒng)本地工具進(jìn)行診斷

最后
歡迎大家一起交流,喜歡文章記得點(diǎn)個(gè)贊喲,感謝支持!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI