JVM垃圾回收

概述

對于java程序員酝豪,在虛擬機自動內(nèi)存管理機制下园骆,不再需要為每一個new操作去寫配對的delete/free代碼,不容易出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出的問題寓调。垃圾回收機制會自動管理jvm內(nèi)存空間,將不會被使用到的垃圾對象清理锄码,釋放空間被其他對象使用夺英。
垃圾收集(Garbage Collection,GC)需要完成3件事情:

  • 哪些內(nèi)存需要回收
  • 什么時候回收
  • 如何回收

引用

判定對象是否存活都與引用有關(guān),在jdk1.2之后滋捶,java對引用的概念進(jìn)行了擴充痛悯,將引用分為強引用(Strong Reference)軟引用(Soft Reference)重窟、弱引用(Weak Reference)载萌、虛引用(Phantom Reference),這4種引用強度依次減弱巡扇。

  • 強引用:強引用在代碼種普遍存在扭仁,例如Object obj = new Object()這類代碼。只要強引用存在厅翔,垃圾收集器永遠(yuǎn)不會回收被引用的對象乖坠。
  • 軟引用:描述一些還有用但非必需的對象。當(dāng)系統(tǒng)內(nèi)存不足時刀闷,在發(fā)生內(nèi)存溢出之前熊泵,這類對象會被垃圾收集器回收。JDK提供SoftReference類實現(xiàn)軟引用甸昏。
  • 弱引用:用于描述非必需對象顽分,強度比軟引用弱。只要發(fā)生GC施蜜,這類對象都會被垃圾收集器回收卒蘸,JDK提供WeakReference類實現(xiàn)弱引用。
  • 虛引用:最弱的一種引用關(guān)系花墩。虛引用不會對象的生存時間構(gòu)成影響悬秉,也無法通過虛引用取得對象實例澄步,它唯一的目的是能在這個對象被垃圾收集器回收時收到一條系統(tǒng)通知。JDK提供PhantomReference類實現(xiàn)虛引用和泌。

如何判定對象是否存活

在堆種村缸,存放著java幾乎所有的對象實例,垃圾收集器對堆進(jìn)行回收前武氓,第一步就是確定哪些對象還“存活”梯皿,哪些對象已經(jīng)“死去”(即不可能再被任何途徑使用的對象)。

引用計數(shù)算法

每個對象有個引用計數(shù)器县恕,每當(dāng)有一個地方引用它時东羹,計數(shù)器值+1;引用失效時忠烛,計數(shù)器值-1属提;任何時刻計數(shù)器值為0的對象就是不可能再被使用的。
優(yōu)點:

  • 實現(xiàn)簡單美尸,判定效率高

缺點:

  • 很難解決對象之間相互循環(huán)引用的問題
/**
 * @Time : 2019/05/12 上午 10:42
 * @Author : xiuc_shi
 **/
public class ReferenceCountingGC {
    private ReferenceCountingGC instance;
    public static void main(String[] args) {
        ReferenceCountingGC a = new ReferenceCountingGC();
        ReferenceCountingGC b = new ReferenceCountingGC();
        a.instance = b;
        b.instance = a;
        a = null;
        b = null;
        System.gc();
    }
}

以上例子冤议,a對象引用了b對象,b對象引用了a對象师坎,但最后a和b都指向null了恕酸,實際上兩個對象不能再被訪問。如果使用引用計數(shù)算法胯陋,它們將不會被回收蕊温。

可達(dá)性分析算法

通過一系列“GC Roots”對象作為起始點,從這些節(jié)點開始向下搜索遏乔,搜索所走過的路徑稱為引用鏈(Reference Chain)义矛,當(dāng)一個對象到GC Roots沒有任何引用鏈時,則證明此對象不可用盟萨。


對象object5症革、object6和object7雖然相互關(guān)聯(lián),但到GC Roots不可達(dá)鸯旁,因此被判定為可回收對象噪矛。
在java語言種,可作為GC Roots的對象包括:

  • 虛擬機棧(棧幀種本地變量表)種引用的對象
  • 方法區(qū)種類靜態(tài)屬性引用的對象
  • 方法區(qū)種常量引用的對象
  • 本地方法棧種JNI(native 方法)引用的對象

即使被可達(dá)性分析算法標(biāo)記為不可達(dá)對象铺罢,對象也并非非死不可艇挨。要真正宣告一個對象死亡,至少要經(jīng)歷兩次標(biāo)記過程:

  • 對象被可達(dá)性分析不可到達(dá)GC Roots時韭赘,它將會被第一次標(biāo)記并進(jìn)行一次篩選缩滨,篩選的條件是此對象是否有必要執(zhí)行finalize()方法。
  • 當(dāng)對象沒有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機調(diào)用過脉漏,則將這兩種情況視為“沒有必要執(zhí)行”苞冯。
  • 如果對象被判定為有必要執(zhí)行finalize()方法,則對象會放置在一個叫做F-Queue的隊列中侧巨,并在稍后由虛擬機自動建立的舅锄、低優(yōu)先級的Finalizer線程執(zhí)行。
  • finalize()方法是對象逃脫死亡命運的最后一次機會司忱,稍后GC將堆F-Queue隊列中的對象進(jìn)行第二次小規(guī)模標(biāo)記皇忿,如果對象在finalize()中重新與引用鏈上的任意一個對象建立關(guān)聯(lián),那么第二次標(biāo)記時它會被移除出“即將回收”的集合坦仍。
  • 這種自救的機會只有一次鳍烁,因為一個對象的finalize()方法最多只會被系統(tǒng)自動調(diào)用一次

垃圾收集算法

標(biāo)記-清除算法

標(biāo)記-清除(Mark-Sweep)算法是最基礎(chǔ)得收集算法繁扎,算法分為“標(biāo)記”和“清除”兩個階段:首先標(biāo)記出所有需要回收得對象幔荒,標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記得對象。
該算法主要有兩個不足:

  • 效率問題:標(biāo)記和清除兩個過程效率都不高梳玫。
  • 空間問題:標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片铺峭,空間碎片太多可能會導(dǎo)致程序分配較大對象時,無法找到足夠的連續(xù)內(nèi)存而出發(fā)另一次收集動作汽纠。


復(fù)制算法

復(fù)制算法是為了解決標(biāo)記-清除算法的效率問題而出現(xiàn)的。它將可用的內(nèi)存容量劃分為大小相等的兩塊傀履,每次只使用其中的一塊虱朵。當(dāng)這塊內(nèi)存用完后,將還存活的對象復(fù)制到另一塊,然后把已使用過的內(nèi)存空間清理。這樣內(nèi)存分配時不用考慮內(nèi)存碎片的問題瘩绒,只需要按順序分配內(nèi)存癌压,實現(xiàn)簡單,運行高效泳梆。但是每次只能使用內(nèi)存空間的一半


實際上,由于新生代中的對象98%都是“朝生夕死”偿荷,所以一般內(nèi)存不是按照“1:1”的比例來劃分的。而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間唠椭,每次使用Eden和其中一塊Survivor跳纳。當(dāng)回收時,將Eden和Survivor中還存活的對象一次性復(fù)制到另一塊Survivor空間上贪嫂,最后將Eden和剛使用過的Survivor空間寺庄。
HotSpot虛擬機默認(rèn)Eden和Survivor的大小比例是8:1,只有10%的空間被“浪費”。

標(biāo)記-整理算法

復(fù)制算法在對象存活率較高時會進(jìn)行較多的復(fù)制操作斗塘,效率將會降低赢织,所以在老年代一般不能直接用復(fù)制算法。
根據(jù)老年代的特點馍盟,標(biāo)記-整理(Mark-Compact)算法被提出于置。它的標(biāo)記過程和標(biāo)記-清除算法一樣,但是后續(xù)不是直接對可回收對象進(jìn)行清理朽合,而是讓所有存活的對象都向一端移動俱两,然后直接清理端邊界以外的內(nèi)存。


分代收集算法

當(dāng)前商業(yè)虛擬機的垃圾收集都采用分代收集(Generational Collection)算法曹步,該算法沒有什么新思想宪彩,只是根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊。一般把java堆分為新生代和老年代讲婚,根據(jù)各個年代的特點采用適當(dāng)?shù)氖占惴ā?/p>

  • 新生代中尿孔,每次垃圾收集時都會發(fā)現(xiàn)大量對象死去,選用復(fù)制算法筹麸,只需要將少量存活對象賦值到另一個空間活合。
  • 老年代中,對象存活率高物赶,不適合使用復(fù)制算法白指,而使用“標(biāo)記-清理”或者“標(biāo)記-整理”算法來進(jìn)行回收。

垃圾收集器

如果說收集算法是垃圾回收的方法論酵紫,那么垃圾收集器就是內(nèi)存回收的具體實現(xiàn)告嘲。

Serial收集器

Serial收集器是最基本、發(fā)展歷史最悠久的收集器奖地,它只使用一個線程去回收內(nèi)存橄唬。新生代采用復(fù)制算法,老年代使用標(biāo)記-整理算法参歹,在內(nèi)存回收過程中會停掉其他所有的工作線程(Stop The World)仰楚,直到收集結(jié)束。



Serial收集器是運行在Client模式下的虛擬機最好垃圾回收器選擇犬庇。

ParNew收集器

ParNew收集器是Serial收集器的多線程版本僧界,除了使用多線程,其他的一些控制參數(shù)臭挽、收集算法捎泻、Stop The World、對象分配規(guī)則埋哟、回收策略等與Serial收集器一樣笆豁。



ParNew收集器是許多運行在Server模式下的虛擬機首選的新生代收集器郎汪。其次,它是除Serial收集器外闯狱,唯一能與CMS收集器配合工作的收集器煞赢。

Parallel Scavenge收集器

Parallel Scavenge收集器是一個新生代收集器,使用了復(fù)制算法哄孤。與其他垃圾收集器的關(guān)注點不同照筑,它的目標(biāo)是達(dá)到一個可控制的吞吐量(Throughput)。
吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)
Parallel Scavenge收集器提供了兩個參數(shù)用于精確控制吞吐量瘦陈,分別為控制最大垃圾收集停頓時間(-XX:MaxGCPauseMillis)和直接設(shè)置吞吐量大小(-XX:GCTimeRatio)凝危,通過動態(tài)調(diào)整這兩個參數(shù)以得到合適的停頓時間和吞吐量。

Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本晨逝,單線程蛾默,使用標(biāo)記-整理算法。它有兩個用途:

  • 和Parallel Scavenge收集器搭配使用
  • 作為CMS收集器的備選方案
Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本捉貌,多線程支鸡,使用標(biāo)記-整理算法。如果選擇了新生代Parallel Scavenge收集器趁窃,那么除了Serial Old收集器外別無選擇牧挣。但是這種組合,由于單線程的老年代收集無法充分發(fā)揮服務(wù)器多核的處理能力醒陆。直到Parallel Old的出現(xiàn)瀑构,在注重吞吐量以及CPU資源敏感的場合,會優(yōu)先考慮Parallel Scavenge+Parallel Old收集器組合刨摩。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器寺晌。java應(yīng)用大多集中在網(wǎng)站或B/S系統(tǒng)的服務(wù)端上,尤其重視服務(wù)的響應(yīng)速度码邻,希望系統(tǒng)的停頓時間最短以提高用戶體驗。
CMS使用了標(biāo)記-清除算法另假,整個過程分為4個步驟:

  • 初始標(biāo)記(CMS initial mark)
  • 并發(fā)標(biāo)記(CMS concurrent mark)
  • 重新標(biāo)記(CMS remark)
  • 并發(fā)清除(CMS concurrent sweep)

初始標(biāo)記像屋、重新標(biāo)記仍然需要Stop The World。初始標(biāo)記僅僅標(biāo)記了一下GC Roots能直接關(guān)聯(lián)到的對象边篮,速度很快己莺;并發(fā)標(biāo)記則是進(jìn)行GC Roots Tracing的過程,重新標(biāo)記是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運作而導(dǎo)致標(biāo)記產(chǎn)生變動的那部分對象的標(biāo)記記錄戈轿。



CMS收集器的優(yōu)點:

  • 并發(fā)收集凌受,低停頓

CMS收集器的缺點:

  • CMS收集器對CPU資源非常敏感。
  • CMS收集器無法處理浮動垃圾思杯。
  • 基于標(biāo)記-清除算法胜蛉,會產(chǎn)生大量空間碎片挠进。
G1收集器

G1(Garbage-First)收集器是當(dāng)今收集器技術(shù)發(fā)展的最前沿成果之一,它是面向服務(wù)器應(yīng)用的垃圾收集器誊册。
與其他GC收集器相比领突,G1具備的特點如下:

  • 并行與并發(fā):充分利用多CPU、多核環(huán)境案怯,縮短Stop The World時間君旦,而且可以并發(fā)執(zhí)行內(nèi)存回收。
  • 分代收集:可以不與其他收集器配合獨立管理整個GC堆嘲碱,采用不同的方式處理新建對象和已存活一段時間金砍、熬過多個GC的舊對象。
  • 空間整合:G1從整體上看基于標(biāo)記-整理算法實現(xiàn)麦锯,局部上看基于復(fù)制算法實現(xiàn)恕稠。因此,不會產(chǎn)生空間碎片离咐。
  • 可預(yù)測的停頓:降低停頓時間是G1和CMS的共同關(guān)注點谱俭;建立了可預(yù)測的停頓時間模型,能讓使用者明確在一個長度為M的時間片內(nèi)宵蛀,消耗在垃圾回收上的時間不超過N昆著,幾乎是實時java(RTSJ)垃圾回收的特征了。

G1之前的收集器進(jìn)行回收的范圍是整個新生代或老年代术陶,而使用G1收集器時凑懂,java堆的內(nèi)存布局與其他收集器有很大的差別,它將整個java堆分為多個大小相等的獨立區(qū)域(Region)梧宫,雖然還保留新生代和老年代的概念接谨,但新生代和老年代不再是物理隔離,它們都是一部分Region(不一定連續(xù))的集合塘匣。

G1收集器可以有計劃地避免在整個java堆中進(jìn)行全區(qū)域的垃圾收集脓豪。G1跟蹤各個Region中的垃圾堆積的價值大小(回收所獲得空間大小以及回收所需時間的經(jīng)驗值),在后臺維護(hù)一個優(yōu)先列表忌卤,每次根據(jù)允許的收集時間扫夜,優(yōu)先回收價值最大的Region。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級的區(qū)域回收方式驰徊,保證了G1收集器在有限時間內(nèi)獲得盡可能高的收集效率笤闯。
G1收集器每個Region有一個與之對應(yīng)的Remembered Set,虛擬機發(fā)現(xiàn)程序在對Reference類型數(shù)據(jù)進(jìn)行寫操作時棍厂,會產(chǎn)生Write Barrier暫時中斷寫操作颗味,檢查Reference引用的對象是否處于不同的Region,如果是,則通過CardTable把相關(guān)信息記錄到被引用對象所屬Region的Remembered Set中牺弹。當(dāng)進(jìn)行內(nèi)存回收時浦马,在GC根節(jié)點的枚舉范圍內(nèi)加入Remembered Set即可保證不對全堆掃面时呀。

G1的運作大致劃分為以下步驟:

  • 初始標(biāo)記(Initial Marking):標(biāo)記GC Roots能直接關(guān)聯(lián)的對象,并修改TAMS(Next Top at Mark Start)的值捐韩,讓下一階段用戶程序并發(fā)運行時退唠,能在正確可用的Region中創(chuàng)建新對象。
  • 并發(fā)標(biāo)記(Concurrent Marking):對堆中對象進(jìn)行可達(dá)性分析荤胁,找到存活的對象瞧预。
  • 最終標(biāo)記(Final Marking):修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運作而導(dǎo)致標(biāo)記產(chǎn)生變化的那一部分標(biāo)記記錄,虛擬機將這段時間對象變化記錄在Remembered Set Logs中仅政,最終標(biāo)記階段將Remembered Set Logs的數(shù)據(jù)合并到Remembered Set垢油。
  • 篩選回收(Live Data Counting and Evacuation):首先對各個Region的回收價值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時間指定回收計劃圆丹。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滩愁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辫封,更是在濱河造成了極大的恐慌硝枉,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倦微,死亡現(xiàn)場離奇詭異妻味,居然都是意外死亡,警方通過查閱死者的電腦和手機欣福,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門责球,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拓劝,你說我怎么就攤上這事雏逾。” “怎么了郑临?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵栖博,是天一觀的道長。 經(jīng)常有香客問我厢洞,道長仇让,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任犀变,我火速辦了婚禮妹孙,結(jié)果婚禮上秋柄,老公的妹妹穿的比我還像新娘获枝。我一直安慰自己,他們只是感情好骇笔,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布省店。 她就那樣靜靜地躺著嚣崭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪懦傍。 梳的紋絲不亂的頭發(fā)上雹舀,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天,我揣著相機與錄音粗俱,去河邊找鬼说榆。 笑死,一個胖子當(dāng)著我的面吹牛寸认,可吹牛的內(nèi)容都是我干的签财。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼偏塞,長吁一口氣:“原來是場噩夢啊……” “哼唱蒸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灸叼,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤神汹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后古今,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屁魏,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年沧卢,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚁堤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡但狭,死狀恐怖披诗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情立磁,我是刑警寧澤呈队,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站唱歧,受9級特大地震影響宪摧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颅崩,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一几于、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沿后,春花似錦沿彭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瞧柔。三九已至,卻和暖如春睦裳,著一層夾襖步出監(jiān)牢的瞬間造锅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工廉邑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留哥蔚,地道東北人。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓蛛蒙,卻偏偏與公主長得像肺素,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宇驾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容

  • 垃圾收集基礎(chǔ) Java 語言的一大特點就是可以進(jìn)行自動垃圾回收處理倍靡,而無需開發(fā)人員過于關(guān)注系統(tǒng)資源,例如內(nèi)存資源的...
    Austin_Brant閱讀 741評論 0 2
  • 0. 前言 JVM筆記系列课舍,以JDK1.7為基準(zhǔn)塌西,主要以《深入理解Java虛擬機》(第二版)和《Java虛擬機規(guī)范...
    郭尋撫閱讀 897評論 0 3
  • 許多高級編程語言都帶有自動垃圾回收特性,以將程序員從繁瑣復(fù)雜的內(nèi)存分配和釋放工作中解脫筝尾。本文將概述常見的垃圾回收算...
    kelgon閱讀 2,047評論 2 52
  • 本節(jié)常見面試題: 問題答案在文中都有提到 如何判斷對象是否死亡(兩種方法)捡需。 簡單的介紹一下強引用、軟引用筹淫、弱引用...
    Java黎先生閱讀 255評論 0 4
  • 寫在前面 本節(jié)常見面試題: 問題答案在文中都有提到 如何判斷對象是否死亡(兩種方法)站辉。 簡單的介紹一下強引用、軟引...
    哈哈大圣閱讀 213評論 0 1