JVM垃圾回收機(jī)制之GC解讀
JVM的垃圾回收機(jī)制:GC,是Java提供的對于內(nèi)存自動回收的機(jī)制。
在 Java 中,所有的對象都是要存在內(nèi)存中的(也可以說內(nèi)存中存儲的是一個個對象),因此將內(nèi)存回收,也可以叫做死亡對象的回收。GC回收的是“堆上的內(nèi)存”。
一、死亡對象的判斷算法
1.1 引用計數(shù)算法
思想:
給對象增加一個引用計數(shù)器,每當(dāng)有一個地方引用它時,計數(shù)器就+1;當(dāng)引用失效時,計數(shù)器就-1; 任何時刻計數(shù)器為0的對象就是不能再被使用的,即對象已"死"。
引用計數(shù)法實(shí)現(xiàn)簡單,判定效率也比較高,在大部分情況下都是一個不錯的算法。比如Python語言就采用引用計數(shù)法進(jìn)行內(nèi)存管理。
在主流的JVM中沒有選用引用計數(shù)法來管理內(nèi)存,最主要的原因就是引用計數(shù)法無法解決對象的循環(huán)引用問題。
1.2 可達(dá)性分析算法
思想:
通過一系列稱為"GC Roots"的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索走過的路徑稱之為"引用鏈",當(dāng)一個對象到 GC Roots 沒有任何的引用鏈相連時 (從GC Roots到這個對象不可達(dá))時,證明此對象是不可用的。如下:
對象 Object5 - Object7 之間雖然彼此還有關(guān)聯(lián),但是它們到 GC Roots 是不可達(dá)的,因此他們會被判定為可回收對象。
在Java語言中,可作為 GC Roots 的對象包含下面幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象;
- 方法區(qū)中類靜態(tài)屬性引用的對象;
- 方法區(qū)中常量引用的對象;
- 本地方法棧中 JNI(Native方法)引用的對象。
在 JDK1.2 時,Java 對引用的概念做了擴(kuò)充,分為以下四種,這四種引用的強(qiáng)度依次遞減:
- 強(qiáng)引用 : 強(qiáng)引用指的是在程序代碼之中普遍存在的,類似于"Object obj = new Object()"這類的引用,只要強(qiáng)引用還存在,垃圾回收器永遠(yuǎn)不會回收掉被引用的對象實(shí)例。
- 軟引用 : 軟引用是用來描述一些還有用但是不是必須的對象。對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出之前,會把這些對象列入回收范圍之中進(jìn)行第二次回收。如果這次回收還是沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。在JDK1.2之后,提供了SoftReference類來實(shí)現(xiàn)軟引用。
- 弱引用 : 弱引用也是用來描述非必需對象的。但是它的強(qiáng)度要弱于軟引用。被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾回收發(fā)生之前。當(dāng)垃圾回收器開始進(jìn)行工作時,無論當(dāng)前內(nèi)容是否夠用,都會回收掉只被弱引用關(guān)聯(lián)的對象。在JDK1.2之后提供了WeakReference類來實(shí)現(xiàn)弱引用。
- 虛引用 : 虛引用也被稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實(shí)例。為一個對象設(shè)置虛引用的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知。在JDK1.2之后,提供了PhantomReference類來實(shí)現(xiàn)虛引用。
二、垃圾回收算法
2.1 標(biāo)記-清除算法
"標(biāo)記-清除"算法是最基礎(chǔ)的收集算法。算法分為"標(biāo)記"和"清除"兩個階段 : 首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。后續(xù)的收集算法都是基于這種思路并對其不足加以改進(jìn)而已。
- 效率問題 : 標(biāo)記和清除這兩個過程的效率都不高。
- 空間問題 : 標(biāo)記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致以后在程序運(yùn)行中需要分配較大對象時,無法找到足夠連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集。
2.2 復(fù)制算法
復(fù)制"算法是為了解決"標(biāo)記-清理"的效率問題。
它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這塊內(nèi)存需要進(jìn)行垃圾回收時,會將此區(qū)域還存活著的對象復(fù)制到另一塊上面,然后再把已經(jīng)使用過的內(nèi)存區(qū)域一次清理掉。這樣做的好處是每次都是對整個半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時也就不需要考慮內(nèi)存碎片等復(fù)雜情況,只需要移動堆頂指針,按順序分配即可。
此算法實(shí)現(xiàn)簡單,運(yùn)行高效。算法的執(zhí)行流程如下圖 :
2.3 標(biāo)記-整理算法
復(fù)制收集算法在對象存活率較高時會進(jìn)行比較多的復(fù)制操作,效率會變低。因此在老年代一般不能使用復(fù)制算法。 針對老年代的特點(diǎn),提出了一種稱之為"標(biāo)記-整理算法"。標(biāo)記過程仍與"標(biāo)記-清除"過程?致,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理,而是讓所有存活對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。
流程圖如下:
2.4 分代算法
分代算法和上面 3 種算法不同,分代算法是通過區(qū)域劃分,實(shí)現(xiàn)不同區(qū)域和不同的垃圾回收策略,從而實(shí)現(xiàn)更好的垃圾回收。
當(dāng)前 JVM 垃圾收集都采用的是"分代收集(Generational Collection)"算法,這個算法并沒有新思想,只是根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊。
一般是把Java堆分為新生代和老年代。在新生代中,每次垃圾回收都有大批對象死去,只有少量存活,因此我們采用復(fù)制算法;而老年代中對象存活率高、沒有額外空間對它進(jìn)行分配擔(dān)保,就必須采用"標(biāo)記-清理"或者"標(biāo)記-整理"算法。
哪些對象會進(jìn)入新生代?哪些對象會進(jìn)入老年代?
- 新生代:一般創(chuàng)建的對象都會進(jìn)入新生代;
- 老年代:大對象和經(jīng)歷了 N 次(一般情況默認(rèn)是 15 次)垃圾回收依然存活下來的對象會從新生代移動到老年代。
面試題 : 請問了解Minor GC和Full GC么,這兩種GC有什么不一樣嗎?
- Minor GC 又稱為新生代GC : 指的是發(fā)生在新生代的垃圾收集。因?yàn)镴ava對象大多都具備朝生夕滅的特性,因此Minor GC(采用復(fù)制算法)非常頻繁,一般回收速度也比較快。
- Full GC 又稱為老年代GC 或者 Major GC : 指發(fā)生在老年代的垃圾收集。出現(xiàn)了Major GC,經(jīng)常會伴隨至少一次的Minor GC (并非絕對,在Parallel Scavenge收集器中就有直接進(jìn)行Full GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。
三、垃圾收集器
收集算法是內(nèi)存回收的方法論,垃圾收集器是內(nèi)存回收的具體實(shí)現(xiàn)。
垃圾收集器的作用:垃圾收集器是為了保證程序能夠正常、持久運(yùn)行的一種技術(shù),它是將程序中不用的死亡對象也就是垃圾對象進(jìn)行清除,從而保證了新對象能夠正常申請到內(nèi)存空間。
以下這些收集器是 HotSpot 虛擬機(jī)隨著不同版本推出的重要的垃圾收集器:
上圖展示了7種作用于不同分代的收集器,如果兩個收集器之間存在連線,就說明他們之間可以搭配使用。所處的區(qū)域,表示它是屬于新生代收集器還是老年代收集器。
- 并行(Parallel) : 指多條垃圾收集線程并行工作,用戶線程仍處于等待狀態(tài)。
- 并發(fā)(Concurrent) : 指用戶線程與垃圾收集線程同時執(zhí)行 (不一定并行,可能會交替執(zhí)行),用戶程序繼續(xù)運(yùn)行,而垃圾收集程序在另外一個CPU上。
- 吞吐量:就是CPU用于運(yùn)行用戶代碼的時間與CPU總消耗時間的比值。
- 吞吐量 = 運(yùn)行用戶代碼時間/(運(yùn)行用戶代碼時間 + 垃圾收集時間)
例如:虛擬機(jī)總共運(yùn)行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。
3.1 CMS收集器(老年代收集器,并發(fā)GC)
特性:
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器。目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時間最短,以給用戶帶來較好的體驗(yàn)。CMS收集器就非常符合這類應(yīng)用的需求。
CMS收集器是基于“標(biāo)記—清除”算法實(shí)現(xiàn)的,它的整個過程分為4個步驟:
- 1. 初始標(biāo)記(CMS initial mark):初始標(biāo)記僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象,速度很快,需要“Stop The World”。
- 2. 并發(fā)標(biāo)記(CMS concurrent mark):并發(fā)標(biāo)記階段就是進(jìn)行GC Roots Tracing的過程。
- 3. 重新標(biāo)記(CMS remark):重新標(biāo)記階段是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,這個階段的停頓時間一般會比初始標(biāo)記階段稍長一些,但遠(yuǎn)比并發(fā)標(biāo)記的時間短,仍然需要“Stop The World”。
- 4. 并發(fā)清除(CMS concurrent sweep):并發(fā)清除階段會清除對象。 由于整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作,所以,從總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的。
優(yōu)點(diǎn):
- CMS是一款優(yōu)秀的收集器,它的主要優(yōu)點(diǎn)在名字上已經(jīng)體現(xiàn)出來了:并發(fā)收集、低停頓。
缺點(diǎn):
- CMS收集器對CPU資源非常敏感;
- CMS收集器無法處理浮動垃圾;
- CMS收集器會產(chǎn)生大量空間碎片。
3.2 G1收集器(唯一一款全區(qū)域的垃圾回收器)
G1(Garbage First)垃圾回收器是用在heap memory很大的情況下,把heap劃分為很多很多的 region塊,然后并行的對其進(jìn)行垃圾回收。
G1垃圾回收器在清除實(shí)例所占用的內(nèi)存空間后,還會做內(nèi)存壓縮。
年輕代垃圾收集 :
在G1垃圾收集器中,年輕代的垃圾回收過程使用復(fù)制算法。把Eden區(qū)和Survivor區(qū)的對象復(fù)制到新的Survivor區(qū)域。 如下圖:
老年代垃收集:
- 對于老年代上的垃圾收集,G1垃圾收集器也分為4個階段,基本跟CMS垃圾收集器一樣,但略有不同:
- 初始標(biāo)記(Initial Mark)階段:同CMS垃圾收集器的Initial Mark階段一樣,G1也需要暫停應(yīng)用程序的執(zhí)行,它會標(biāo)記從根對象出發(fā),在根對象的第一層孩子節(jié)點(diǎn)中標(biāo)記所有可達(dá)的對象。但是G1的垃圾收集器的Initial Mark階段是跟minor gc一同發(fā)生的。也就是說,在G1中,你不用像在CMS那樣,單獨(dú)暫停應(yīng)用程序的執(zhí)行來運(yùn)行Initial Mark階段,而是在G1觸發(fā)minor gc的時候一并將年老代上的Initial Mark給做了。
- 并發(fā)標(biāo)記(Concurrent Mark)階段:在這個階段G1做的事情跟CMS一樣。但G1同時還多做了一件事情,就是如果在Concurrent Mark階段中,發(fā)現(xiàn)哪些Tenured region中對象的存活率很小或者基本沒有對象存活,那么G1就會在這個階段將其回收掉,而不用等到后面的clean up階段。這也是Garbage First名字的由來。同時,在該階段,G1會計算每個 region的對象存活率,方便后面的clean up階段使用 。
- 最終標(biāo)記(CMS中的Remark階段):在這個階段G1做的事情跟CMS一樣, 但是采用的算法不同,G1采用一種叫做SATB(snapshot-at-the-begining)的算法能夠在Remark階段更快的標(biāo)記可達(dá)對象。
- 篩選回收(Clean up/Copy)階段:在G1中,沒有CMS中對應(yīng)的Sweep階段。相反,它有一個Clean up/Copy階段,在這個階段中,G1會挑選出那些對象存活率低的region進(jìn)行回收,這個階段也是和minor gc一同發(fā)生的,如下圖所示:
總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot集成Aviator實(shí)現(xiàn)參數(shù)校驗(yàn)的代碼工程
Aviator是一個高性能、輕量級的java語言實(shí)現(xiàn)的表達(dá)式求值引擎,主要用于各種表達(dá)式的動態(tài)求值,本文給大家詳細(xì)介紹了SpringBoot集成Aviator實(shí)現(xiàn)參數(shù)校驗(yàn)的方法,并通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-11-11Netty分布式解碼器讀取數(shù)據(jù)不完整的邏輯剖析
這篇文章主要為大家介紹了Netty分布式解碼器讀取數(shù)據(jù)不完整的邏輯剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03postman測試傳入List<String>參數(shù)方式
這篇文章主要介紹了postman測試傳入List<String>參數(shù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08IDEA創(chuàng)建web項(xiàng)目出現(xiàn)404錯誤解決方法
今天先來搭建一個web工程,工程搭建好運(yùn)行時發(fā)現(xiàn)404,本文主要介紹了IDEA創(chuàng)建web項(xiàng)目出現(xiàn)404錯誤解決方法,具有一定的參考價值,感興趣的可以了解一下2023-09-09Spring Boot 整合mybatis 與 swagger2
之前使用springMVC+spring+mybatis,總是被一些繁瑣的xml配置,還經(jīng)常出錯,下面把以前的一些ssm項(xiàng)目改成了spring boot + mybatis,相對于來說優(yōu)點(diǎn)太明顯了,具體內(nèi)容詳情大家通過本文學(xué)習(xí)吧2017-08-08JavaWeb實(shí)現(xiàn)文件上傳下載功能實(shí)例詳解
這篇文章主要介紹了JavaWeb中的文件上傳和下載功能的實(shí)現(xiàn),在開發(fā)中,文件上傳和下載功能是非常常用的功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11