堆内存常见的分配策略、 经典的垃圾收集器、CMS与G1收集器及二者的比较
堆內(nèi)存常見(jiàn)的分配策略
針對(duì)的是Serial 加 Serial Old 客戶(hù)端默認(rèn)收集器組合下的內(nèi)存分配和回收策略
經(jīng)典的垃圾收集器
CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的垃圾收集器。從名字可以看出,CMS 是基于標(biāo)記-清除算法的。它的運(yùn)作過(guò)程主要分為四個(gè)步驟:
初始標(biāo)記(CMS initial mark):STW,標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快。單線程
并發(fā)標(biāo)記(CMS concurrent mark):從GC Roots的直接關(guān)聯(lián)對(duì)象開(kāi)始遍歷整個(gè)對(duì)象圖的過(guò)程,耗時(shí)較長(zhǎng),不需要停頓用戶(hù)線程
重新標(biāo)記(CMS remark):STW,修正并發(fā)標(biāo)記期間,因用戶(hù)程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記發(fā)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄(增量更新),時(shí)間稍長(zhǎng)于初始標(biāo)記,但遠(yuǎn)低于并發(fā)標(biāo)記
并發(fā)清除(CMS concurrent sweep):清除已死亡對(duì)象,因?yàn)椴恍枰苿?dòng)對(duì)象,所以與用戶(hù)線程是并發(fā)的關(guān)系
CMS 的缺點(diǎn):
CMS收集器對(duì)處理器資源敏感。在并發(fā)階段,雖然不會(huì)導(dǎo)致用戶(hù)停頓(STW),但會(huì)占用一部分線程(計(jì)算機(jī)資源)導(dǎo)致應(yīng)用程序變慢,吞吐量降低。CMS默認(rèn)開(kāi)啟回收線程數(shù)是 (處理器核心數(shù)量 + 3)/4。當(dāng)處理器核心數(shù)量大于4個(gè)時(shí),垃圾回收線程只占不超過(guò) 25%的處理器資源,且隨著處理器核心數(shù)量增加而降低。但處理器核心數(shù)量不足4個(gè)時(shí),會(huì)嚴(yán)重影響用戶(hù)程序。為了緩解這個(gè)問(wèn)題,虛擬機(jī)提供了增量式并發(fā)收集器的CMS變種,在并發(fā)標(biāo)記和并發(fā)清理過(guò)程中,垃圾收集線程和用戶(hù)線程交替運(yùn)行,但效果一般,已標(biāo)記為廢棄。
無(wú)法處理“浮動(dòng)垃圾”,有可能出現(xiàn) Concurrent Mode Failure失敗,導(dǎo)致進(jìn)一步 STW 的Full GC。在并發(fā)標(biāo)記和并發(fā)清理過(guò)程中,用戶(hù)線程還在運(yùn)行,自然還會(huì)伴隨新的垃圾對(duì)象的產(chǎn)生,但該部分的垃圾對(duì)象出現(xiàn)在標(biāo)記過(guò)程結(jié)束以后,CMS無(wú)法在當(dāng)次收集過(guò)程中處理他們(只能下次垃圾收集時(shí)處理),因此稱(chēng)為浮動(dòng)垃圾。因此,CMS必須預(yù)留足夠的內(nèi)存空間提供給用戶(hù)線程使用,JDK6,默認(rèn)啟動(dòng)閾值為 92%(該值需要根據(jù)生產(chǎn)環(huán)境變化,太高,有可能頻繁并發(fā)失敗):要是CMS運(yùn)行期間預(yù)留的內(nèi)存無(wú)法滿(mǎn)足程序分配新對(duì)象,觸發(fā)并發(fā)失敗(Concurrent Mode Failure),這時(shí)候JVM啟動(dòng)預(yù)備方案:凍結(jié)用戶(hù)線程,臨時(shí)啟動(dòng) Serial Old 來(lái)進(jìn)行老年代的手機(jī),這樣停頓時(shí)間會(huì)很長(zhǎng)。
大量空間碎片。空間碎片過(guò)多會(huì)導(dǎo)致大對(duì)象分配失敗(老年代還有很多空間,但沒(méi)有連續(xù)的大空間),而不得不提前觸發(fā) Full GC。CMS提供了參數(shù) -XX:+UseCMS-CompactAtFullCollection 開(kāi)關(guān)參數(shù)(默認(rèn)開(kāi)啟,JDK9之后廢棄),用于在CMS不得不Full GC時(shí)開(kāi)啟碎片的合并整理過(guò)程。但整理涉及到移動(dòng)對(duì)象,STW,停頓時(shí)間會(huì)變長(zhǎng)。
Garbage First (G1收集器)
目標(biāo)是在延遲可控的范圍內(nèi)獲取盡可能高的吞吐量
G1是面向堆內(nèi)任何部分來(lái)組成回收集進(jìn)行回收,衡量標(biāo)準(zhǔn)不再是它屬于哪個(gè)分代,而是哪塊內(nèi)存中存放的垃圾最多,回收收益最大,這就是 G1 收集器的 Mixed GC 模式。
G1不再堅(jiān)持分代劃分,而是把連續(xù)的Java 堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),每個(gè) Region 根據(jù)需要版本新生代的Eden、Survivor或老年代。收集器能夠?qū)Π缪莶煌巧腞egion采用不同的策略去處理。這樣無(wú)論是新對(duì)象還是已經(jīng)存活了一段時(shí)間的對(duì)象、熬過(guò)多次收集的就對(duì)象都能獲得很好的收集效果。
Region 中還有一類(lèi)特殊的 Humonggous 區(qū)域,專(zhuān)門(mén)用來(lái)存放大對(duì)象。此處大對(duì)象,指的是內(nèi)存大小超過(guò)一個(gè) Region 容量一半的對(duì)象。每個(gè)Region的大小可以通過(guò) -XX:G1HeapRegionSize 設(shè)定,取值范圍為 1~32MB,且為2的N次冪。如果對(duì)象大小超過(guò)Region大小,將會(huì)被放到N個(gè)連續(xù)的 Humongous Region,G1的大多數(shù)行為將Humongous 作為老年代的一部分處理。
雖然 G1 仍然保留了新生代和老年代的概念,但新生代和老年代不再是固定的,他們都是一系列區(qū)域(不需要連續(xù))的動(dòng)態(tài)集合。G1 之所以能夠建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗麑egion 作為可回收的最小單元。更具體的操作是:讓G1 去跟蹤各個(gè)Region 里面的垃圾堆積的“價(jià)值”大小,價(jià)值即回收所獲得的空間大小以及回收所需要的經(jīng)驗(yàn)值,然后在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶(hù)設(shè)定的允許的收集停頓時(shí)間(-XX:MaxGCPauseMills,默認(rèn)200ms)優(yōu)先處理回收價(jià)值最大的Region。這也是 “Garbage First“ 的由來(lái)。該方式保證了G1在有限的事件內(nèi)獲取盡可能高的收集效率。
G1收集器大致可以分為以下四個(gè)步驟:
初始標(biāo)記(initial marking):標(biāo)記GC Roots 直接關(guān)聯(lián)的對(duì)象,并修改 TAMS 指針的值,讓下一階段用戶(hù)線程并發(fā)運(yùn)行時(shí),能正確的在可用的 Region中分配新對(duì)象。STW,耗時(shí)很短。
并發(fā)標(biāo)記(concurrent marking):從 GC Roots 開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,遞歸掃面整個(gè)堆里的對(duì)象圖。耗時(shí)長(zhǎng),但可與用戶(hù)線程并發(fā)執(zhí)行。對(duì)象圖掃描完成后,還要重新處理 SATB記錄下的在并發(fā)時(shí)有引用變動(dòng)的對(duì)象。
最終標(biāo)記(final marking):暫停用戶(hù)線程(很短),用于處理并發(fā)階段結(jié)束后,遺留下來(lái)的最后少量的SATB記錄。
篩選回收(Live data counting and evacuation 疏散、撤退):負(fù)責(zé)更新 Region 的統(tǒng)計(jì)數(shù)據(jù),對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶(hù)期望的停頓時(shí)間來(lái)指定計(jì)劃,可自由選擇任意多個(gè) Region 進(jìn)行回收。把決定回收的那部分的Region的存活對(duì)象復(fù)制到空的Region中,然后清理掉整個(gè)舊Region的空間。因?yàn)樯婕皩?duì)象的移動(dòng),所以需要STW,暫停用戶(hù)線程,由多條收集線程并行完成。
只有并發(fā)標(biāo)記可以與用戶(hù)線程并發(fā)進(jìn)行。
CMS 與 G1 比較
G1 的優(yōu)點(diǎn)
收集算法不同,CMS 是標(biāo)記—清除,G1從整體來(lái)看是基于”標(biāo)記—整理“的,但從局部看(兩個(gè)Region之間),又是基于”復(fù)制“的。這意味著,G1不會(huì)產(chǎn)生內(nèi)存碎片,垃圾回收完成后內(nèi)存規(guī)整,在程序?yàn)榇髮?duì)象分配內(nèi)存時(shí)不容易因?yàn)闊o(wú)法找到連續(xù)內(nèi)存空間而提前觸發(fā)Full GC
可以指定最大停頓時(shí)間
分 Region 的內(nèi)存布局
按受益動(dòng)態(tài)確定回收集
G1 的缺點(diǎn):
就內(nèi)存占用而言,雖然 G1 和 CMS 都使用卡表來(lái)處理跨代指針,但G1 的實(shí)現(xiàn)更復(fù)雜,而且每個(gè)Region都需要維護(hù)一份卡表。而CMS 的卡表只有唯一一份,只需要處理老年代到新生代的引用。這導(dǎo)致G1的記憶集(和其他內(nèi)存消耗)會(huì)占整個(gè)堆容量的 20%甚至更多。
就執(zhí)行負(fù)載而言,兩個(gè)收集器各自的實(shí)現(xiàn)細(xì)節(jié)導(dǎo)致了用戶(hù)程序運(yùn)行時(shí)的負(fù)載會(huì)有不同。譬如,兩者都用到寫(xiě)屏障,CMS 使用寫(xiě)后屏障來(lái)維護(hù)卡表;而G1不僅使用了寫(xiě)后屏障來(lái)維護(hù)卡表,為了實(shí)現(xiàn)原始快照搜索算法(SATB),還需要使用寫(xiě)前屏障來(lái)跟蹤并發(fā)時(shí)的指針變化情況。相比增量更新算法,原始快照搜索算法能夠減少并發(fā)標(biāo)記和重新標(biāo)記階段的消耗,避免CMS 在最終標(biāo)記階段停留時(shí)間過(guò)長(zhǎng)的缺點(diǎn),但在用戶(hù)程序運(yùn)行期間確實(shí)會(huì)產(chǎn)生由跟蹤引用變化帶來(lái)的額外負(fù)擔(dān)。由于G1對(duì)寫(xiě)屏障的復(fù)雜操作要比CMS消耗更多的運(yùn)算資源,所以CMS的寫(xiě)屏障實(shí)現(xiàn)是直接的同步操作,而G1采用的是類(lèi)似消息隊(duì)列的結(jié)構(gòu),把寫(xiě)前屏障和寫(xiě)后屏障中要做的事放到隊(duì)列里,然后異步處理。
CMS 更適合在小內(nèi)存應(yīng)用上,而G1在大內(nèi)存應(yīng)用上能發(fā)揮更多優(yōu)勢(shì)。兩者的內(nèi)存臨界點(diǎn)為 6 GB 到 8GB。
總結(jié)
以上是生活随笔為你收集整理的堆内存常见的分配策略、 经典的垃圾收集器、CMS与G1收集器及二者的比较的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 数据链路层介质访问控制——信道划分、随机
- 下一篇: Python基础语法:数据类型、进制转换