學(xué)習(xí)java一定要知道的垃圾收集器
垃圾收集器如何演化的?
垃圾收集器的發(fā)展路線,簡單來說是隨著內(nèi)存越來越大而發(fā)生變化。
從分代算法逐漸演化為不分代算法。
從serial的幾十兆,逐漸演化到parallel的幾個(gè)G,再到CMS的幾十個(gè)G,也從此開始了并發(fā)回收。
年輕代收集器
Serial
特點(diǎn):年輕代、串行回收、STW、簡單高效
Serial(串行)收集器是最基本、發(fā)展歷史最悠久的收集器,它是采用復(fù)制算法的新生代收集器,曾經(jīng)(JDK 1.3.1之前)是虛擬機(jī)新生代收集的唯一選擇。
在垃圾回收時(shí),必須暫停其他所有線程的工作線程,即所謂的“Stop The World”。
Serial收集器是HotSpot虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器。
Serial收集器具有簡單而高效,由于沒有線程交互的開銷,可以獲得最高的單線程收集效率(在單個(gè)CPU環(huán)境中)。
添加該參數(shù)來顯式的使用Serial垃圾收集器:
-XX:+UseSerialGC
ParNew
特點(diǎn):年輕代、多線程回收、STW、配合CMS、核心越多效率越高
ParNew收集器是Serial收集器的多線程版本,除了使用多條線程進(jìn)行垃圾收集之外,其余行為包括Serial收集器可用的所有控制參數(shù)、收集算法、Stop The Word、對象分配規(guī)則、回收策略等都與Serial收集器一樣。
目前只有它能和CMS收集器(Concurrent Mark Sweep)配合工作。
在單CPU的環(huán)境下,其性能絕對不會(huì)有比Serial收集器有更好的效果,甚至由于多線程的切換開銷,在雙核情況下也有可能無法做到超越Serial。 在多核的情況下,GC線程數(shù)默認(rèn)與核心數(shù)相同,隨著核心的增多,能獲得更好的效果。
指定使用CMS后,會(huì)默認(rèn)使用ParNew作為新生代收集器:
-XX:+UseConcMarkSweepGC
強(qiáng)制指定使用ParNew
-XX:+UseParNewGC
指定垃圾收集的線程數(shù)量,ParNew默認(rèn)開啟的收集線程與CPU的數(shù)量相同:
-XX:ParallelGCThreads
Parallel Scavenge
特點(diǎn):年輕代、并行回收、吞吐量、GC自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics)
Parallel Scavenge收集器是一個(gè)新生代收集器,使用復(fù)制算法,且是并行的多線程收集器。
Parallel Scavenge收集器關(guān)注點(diǎn)是達(dá)到一個(gè)可控制的吞吐量:
(吞吐量 = 運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間))
而其他收集器關(guān)注點(diǎn)在盡可能的縮短垃圾收集時(shí)用戶線程的停頓時(shí)間。
控制最大垃圾收集停頓時(shí)間:參數(shù)允許的值是一個(gè)大于0的毫秒數(shù),收集器將盡可能的保證內(nèi)存垃圾回收花費(fèi)的時(shí)間不超過設(shè)定的值(但是,并不是越小越好,GC停頓時(shí)間縮短是以犧牲吞吐量和新生代空間來換取的,如果設(shè)置的值太小,將會(huì)導(dǎo)致頻繁GC,這樣雖然GC停頓時(shí)間下來了,但是吞吐量也下來了)
-XX:MaxGCPauseMillis
控制吞吐量大小:參數(shù)的值是一個(gè)大于0且小于100的整數(shù),也就是垃圾收集時(shí)間占總時(shí)間的比率,默認(rèn)值是99,就是允許最大1%(即1/(1+99))的垃圾收集時(shí)間。
-XX:GCTimeRatio
GC自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics) :當(dāng)這個(gè)參數(shù)打開之后,就不需要手工指定新生代的大小、Eden與Survivor區(qū)的比例、晉升老年代對象年齡等細(xì)節(jié)參數(shù)了,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大的吞吐量,這種調(diào)節(jié)方式稱為GC自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics)。只需要把基本的內(nèi)存數(shù)據(jù)設(shè)置好(如-Xmx設(shè)置最大堆),然后使用MaxGVPauseMillis參數(shù)或GCTimeRation參數(shù)給虛擬機(jī)設(shè)立一個(gè)優(yōu)化目標(biāo),JVM會(huì)自動(dòng)調(diào)節(jié)其他優(yōu)化參數(shù)
-XX:+UseAdaptiveSizePolicy
老年代收集器
SerialOld
特點(diǎn):老年代、串行回收、STW
Serial Old收集器是Seria收集器的老年代版本,他同樣是一個(gè)單線程收集器,使用" 標(biāo)記-整理" 算法。
Serial Old收集器主要用于Client模式下的虛擬機(jī)使用。
Server模式下的兩大用途:
- 在JDK1.5及之前的版本與Parallel Scavenge收集器搭配使用;
- 作為CMS收集器的后備方案,在并發(fā)收集發(fā)生Conturrent Mode Failure時(shí)使用。
結(jié)合Serial的回收流程如下圖所示:
ParallelOld
特點(diǎn):老年代、多線程、STW、吞吐量
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法。此收集器的出現(xiàn)代表著“吞吐量優(yōu)先”收集器終于有了比較名副其實(shí)的應(yīng)用組合,在注重吞吐量以及CPU資源敏感的場合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器。
使用po收集參數(shù):
-XX:+UseParallelOldGC
與Parallele Scavenge結(jié)合使用的回收流程如下:
使用吞吐量收集器的吞吐量計(jì)算方式如下圖所示:
CMS(ConcurrentMarkSweep)
特點(diǎn):老年代、并發(fā)的,短停頓(降低STW時(shí)間)
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,它非常符合那些集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端上的Java應(yīng)用,這些應(yīng)用都非常重視服務(wù)的響應(yīng)速度。從名字上(“Mark Sweep”)就可以看出它是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的。
CMS問題比較多,所以現(xiàn)在沒有一個(gè)版本默認(rèn)是CMS,只能手工指定。
CMS既然是MarkSweep,就一定會(huì)有碎片化的問題,碎片到達(dá)一定程度,CMS的老年代分配對象分配不下的時(shí)候,使用SerialOld 進(jìn)行老年代回收。
CMS的回收過程主要分為4個(gè)步驟:
- 初始標(biāo)記 標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象,速度很快,需要“Stop The World”。
- 并發(fā)標(biāo)記 進(jìn)行GC Roots Tracing(根可達(dá)算法)的過程,在整個(gè)過程中耗時(shí)最長,標(biāo)記所有根節(jié)點(diǎn)能照的對象。與用戶線程一起工作。
- 重新標(biāo)記 為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段稍長一些,但遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短。此階段也需要“Stop The World”。
- 并發(fā)清理 與用戶線程一起運(yùn)行。
從以上過程中可以總結(jié)出其優(yōu)點(diǎn):并發(fā)收集,并發(fā)清理,地停頓。
三個(gè)缺點(diǎn):
- CMS收集器對CPU資源非常敏感。 CMS默認(rèn)啟動(dòng)的回收線程數(shù)是(CPU數(shù)量+3)/4,回收線程會(huì)占用部分線程資源,導(dǎo)致系統(tǒng)吞吐量降低。在選用CMS收集器的時(shí)候,需要考慮,當(dāng)前的應(yīng)用系統(tǒng),是否對CPU資源敏感。
- 垃圾收集過程中,無法處理浮動(dòng)垃圾,可能會(huì)出現(xiàn)Concurrent Mode Failure問題,導(dǎo)致觸發(fā)Full GC。
- 浮動(dòng)垃圾:是由于CMS收集器的并發(fā)清理階段,清理線程是和用戶線程一起運(yùn)行,如果在清理過程中,用戶線程產(chǎn)生了垃圾對象,由于過了標(biāo)記階段,所以這些垃圾對象就成為了浮動(dòng)垃圾。
- CMS無法在當(dāng)前垃圾收集過程中集中處理這些浮動(dòng)垃圾,需要預(yù)留內(nèi)存空間去保存這些垃圾,可以通過參數(shù) -XX:CMSInitiatingOccupancyFraction 控制觸發(fā)占用老年代的百分比。
- 如果預(yù)留空間無法裝下浮動(dòng)垃圾,會(huì)導(dǎo)致Concurrent Mode Failure失敗,這個(gè)時(shí)候,虛擬機(jī)將啟動(dòng)后備預(yù)案,臨時(shí)啟動(dòng)Serial Old收集器來對老年代重新進(jìn)行垃圾收集,這樣會(huì)導(dǎo)致垃圾收集的時(shí)間邊長,特別是當(dāng)老年代內(nèi)存很大的時(shí)候。所以對參數(shù)-XX:CMSInitiatingOccupancyFraction的設(shè)置,過高,會(huì)導(dǎo)致發(fā)生Concurrent Mode Failure,過低,則浪費(fèi)內(nèi)存空間。
- 使用的"標(biāo)記-清除"算法會(huì)出現(xiàn)很多內(nèi)存碎片。 過多的內(nèi)存碎片導(dǎo)致缺少連續(xù)的內(nèi)存空間,會(huì)影響大對象的分配,最終觸發(fā)Full GC,為了解決這個(gè)問題,CMS收集器提供了一個(gè)"-XX:+UseCMSCompactAtFullCollection"參數(shù)(默認(rèn)是開啟的),用于CMS收集器在必要的時(shí)候?qū)?nèi)存碎片進(jìn)行壓縮整理。由于內(nèi)存碎片整理過程不是并發(fā)的,所以會(huì)導(dǎo)致停頓時(shí)間變長。虛擬機(jī)還提供了一個(gè)-XX:CMSFullGCsBeforeCompaction"參數(shù),來控制進(jìn)行過多少次不壓縮的Full GC以后,進(jìn)行一次帶壓縮的Full GC,默認(rèn)值是0,表示每次在進(jìn)行Full GC前都進(jìn)行碎片整理。
主要參數(shù): 使用CMS收集器:
-XX:+UseConcMarkSweepGC
Full GC后,進(jìn)行一次碎片整理;整理過程是獨(dú)占的,會(huì)引起停頓時(shí)間變長:
-XX:+UseCMSCompactAtFullCollection
設(shè)置進(jìn)行幾次Full GC后,進(jìn)行一次碎片整理:
-XX:+CMSFullGCsBeforeCompaction
設(shè)定CMS的線程數(shù)量(一般情況約等于可用CPU數(shù)量):
-XX:ParallelCMSThreads
新型收集器
G1
算法:三色標(biāo)記 + SATB
G1(Garbage-First)收集器是當(dāng)今收集器技術(shù)發(fā)展最前沿的成果之一,它是一款面向服務(wù)端應(yīng)用的垃圾收集器,HotSpot開發(fā)團(tuán)隊(duì)賦予它的使命是(在比較長期的)未來可以替換掉JDK 1.5中發(fā)布的CMS收集器。G1從JDK9開始已經(jīng)作為默認(rèn)的垃圾回收器。如果對于應(yīng)用程序來說停頓時(shí)間比吞吐量更重要,G1是非常合適的選擇。
G1與上面介紹的GC有很大的不同:
- G1的設(shè)計(jì)原則是首先收集盡可能多的垃圾(Garbage First) 。 G1并不會(huì)等內(nèi)存耗盡(串行、并行)或者快耗盡(CMS)的時(shí)候開始垃圾收集,而是在內(nèi)部采用了啟發(fā)式算法,在老年代找出具有高收集收益的分區(qū)進(jìn)行收集。
- G1采用內(nèi)存分區(qū)(Region)的思路
- 邏輯上的分代概念,不是物理分代
- 所有收集都是STW,混合(mixed)收集(年輕代和老年代同時(shí)進(jìn)行收集)
三色標(biāo)記法 說起并發(fā)回收,就不得不了解三色標(biāo)記法。先學(xué)習(xí)三色標(biāo)記法,更好的理解G1的分區(qū)模型。
我們將對象分成三種類型:
- 黑色:跟對象或者該對象與它的子對象都被掃描(標(biāo)記完成)。
- 灰色:對象本身被標(biāo)記完成,但是還沒有掃描完該對象中的子對象
- 白色:未被掃描的對象。或者是掃描完成后,最終白色對象為不可達(dá)對象即為垃圾對象。
其實(shí)現(xiàn)過程如下圖所示:
- 第一步:根對象被置為黑色,子節(jié)點(diǎn)置為灰色。
- 第二步:繼續(xù)遍歷灰色對象,將遍歷過的子節(jié)點(diǎn)置為灰色,當(dāng)前灰節(jié)點(diǎn)置為黑色。
- 第三部:遍歷所有可達(dá)對象后,所有可達(dá)對象置為黑色,不可達(dá)的為白色,將要被回收。
三色標(biāo)記存在的問題 如下圖所示,如果gc掃描到左側(cè)位置,這時(shí)候程序執(zhí)行C.d = D,B.d = null,則會(huì)導(dǎo)致B對D的引用消失,而C則引用了D,可是此時(shí)C是黑色的,不會(huì)對其子節(jié)點(diǎn)進(jìn)行掃描,則D節(jié)點(diǎn)會(huì)被當(dāng)做垃圾進(jìn)行回收。
三色標(biāo)記法問題解決方案
通常有兩種解決方案:
- 在插入的時(shí)候記錄對象(CMS增量更新)
- 在刪除的時(shí)候記錄對象(G1 STAB)
增量更新(Incremental update) : 在CMS采用的是增量更新(Incremental update),只要在寫屏障(write barrier)里發(fā)現(xiàn)要有一個(gè)白對象的引用被賦值到一個(gè)黑對象 的字段里,那就把這個(gè)白對象變成灰色的。即插入的時(shí)候記錄下來。
STAB(snapshot-at-the-beginning) : STAB 的做法在 GC 開始時(shí),在初始標(biāo)記時(shí)對內(nèi)存進(jìn)行一個(gè)對象圖的邏輯快照 (snapshot),只要被快照到對象是活的,那在整個(gè) GC 的過程中對象就被認(rèn)定的是活的,即使該對象的引用稍后被修改或者刪除。
同時(shí)新分配的對象也會(huì)被認(rèn)為是活的,除此之外其它不可達(dá)的對象就被認(rèn)為是死掉了。這樣 STAB 就保證了真正存活的對象不會(huì)被 GC 誤回收,但同時(shí)也造成了某些可以被回收的對象逃過了 GC,導(dǎo)致了內(nèi)存里面存在浮動(dòng)的垃圾 (float garbage),這些浮動(dòng)垃圾將在下次回收時(shí)被收集。
G1的分區(qū)
G1是邏輯分代,將堆分為若干個(gè)區(qū)域(region),新生代的垃圾收集依然采用暫停所有應(yīng)用線程的方式,將存活對象拷貝到老年代或者Survivor空間。老年代也分成很多區(qū)域,G1收集器通過將對象從一個(gè)區(qū)域復(fù)制到另外一個(gè)區(qū)域,完成了清理工作。這樣就解決了CMS中的內(nèi)存碎片問題。
如上圖分代模型所示,G1仍然有Eden,Survivor,Old等區(qū)域,同時(shí)多了一個(gè)Humongous Region(巨型對象)。
巨型對象:一個(gè)大小達(dá)到甚至超過分區(qū)大小一半的對象稱為巨型對象(Humongous Object)。
巨型對象:”會(huì)獨(dú)占一個(gè)、或多個(gè)連續(xù)分區(qū),其中第一個(gè)分區(qū)被標(biāo)記為開始巨型(StartsHumongous),相鄰連續(xù)分區(qū)被標(biāo)記為連續(xù)巨型(ContinuesHumongous)。巨型對象會(huì)直接在老年代分配,所占用的連續(xù)空間稱為巨型分區(qū)(Humongous Region)。G1內(nèi)部做了一個(gè)優(yōu)化,一旦發(fā)現(xiàn)沒有引用指向巨型對象,則可直接在年輕代收集周期中被回收。
G1中GC的分類:
- Young GC:主要是對Eden區(qū)進(jìn)行GC,它在Eden空間耗盡時(shí)會(huì)被觸發(fā)。 在這種情況下,Eden空間的數(shù)據(jù)移動(dòng)到Survivor空間中,如果Survivor空間不夠,Eden空間的部分?jǐn)?shù)據(jù)會(huì)直接晉升到年老代空間。Survivor區(qū)的數(shù)據(jù)移動(dòng)到新的Survivor區(qū)中,也有部分?jǐn)?shù)據(jù)晉升到老年代空間中。最終Eden空間的數(shù)據(jù)為空,GC停止工作,應(yīng)用線程繼續(xù)執(zhí)行
- Mix GC:不僅進(jìn)行正常的新生代垃圾收集,同時(shí)也回收部分后臺(tái)掃描線程標(biāo)記的老年代分區(qū)。
G1是否有Full GC? 答案是肯定的,G1觸發(fā)了Full GC,這時(shí)G1會(huì)退化使用Serial Old收集器來完成垃圾的清理工作。
Full GC產(chǎn)生的原因? G1在對象復(fù)制/轉(zhuǎn)移失敗或者沒法分配足夠內(nèi)存(比如巨型對象沒有足夠的連續(xù)分區(qū)分配)時(shí),會(huì)觸發(fā)Full GC。Full GC使用的是stop the world的單線程的Serial Old模式,所以一旦觸發(fā)Full GC則會(huì)STW應(yīng)用線程,并且執(zhí)行效率很慢。JDK 8版本的G1是不提供Full GC的處理的。對于G1 GC的優(yōu)化,很大的目標(biāo)就是沒有Full GC。
剩下三種GC本文會(huì)在后面主鍵補(bǔ)充:
- ZGC (10ms - 1ms) PK C++:算法:ColoredPointers + LoadBarrier
- Shenandoah:算法:ColoredPointers + WriteBarrier
- Eplison:Epsilon不回收內(nèi)存,只負(fù)責(zé)堆的管理與布局,對象的分配,與解釋器、編譯器、監(jiān)控子系統(tǒng)的協(xié)作; epsilon適合運(yùn)行時(shí)間短、在內(nèi)存耗盡前就可退出的應(yīng)用程序
到此這篇關(guān)于學(xué)習(xí)java一定要知道的垃圾收集器的文章就介紹到這了,更多相關(guān)java垃圾收集器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用springBoot中的info等級通過druid打印sql
這篇文章主要介紹了使用springBoot中的info等級通過druid打印sql,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Java基礎(chǔ)之static關(guān)鍵字的使用講解
這篇文章主要介紹了Java基礎(chǔ)之static關(guān)鍵字的使用講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07詳解SpringBoot 快速整合MyBatis(去XML化)
本篇文章主要介紹了詳解SpringBoot 快速整合MyBatis(去XML化),非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10SpringBoot2整合activiti6環(huán)境搭建過程解析
這篇文章主要介紹了SpringBoot2整合activiti6環(huán)境搭建過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Java設(shè)計(jì)模式之迭代器模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java設(shè)計(jì)模式之迭代器模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理,需要的朋友可以參考下2017-08-08