JVM03--JVM垃圾收集機(jī)制的一些基本概念

前言

今天來(lái)學(xué)習(xí)下與JVM垃圾收集機(jī)制相關(guān)的一些基本概念。
如何判斷對(duì)象是否存活

垃圾收集器首要的任務(wù)的任務(wù)就是判斷哪些對(duì)象是存活的,哪些對(duì)象已經(jīng)死去了(這里死去的意思是對(duì)象不再被任何途徑使用)。
引用計(jì)數(shù)算法

引用計(jì)數(shù)算法是在對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加一;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減一;任何時(shí)刻計(jì)數(shù)器為零的對(duì)象就是不可能被使用的對(duì)象。
引用計(jì)數(shù)算法的缺點(diǎn)就是很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題。
可達(dá)性分析算法

通過(guò)一系列稱為“GC Roots”的根對(duì)象作為起始節(jié)點(diǎn)集,從這些節(jié)點(diǎn)開(kāi)始,根據(jù)引用關(guān)系向下搜索,搜索的過(guò)程所走過(guò)的路徑稱為“引用鏈”,如果某個(gè)對(duì)象到GC Roots間沒(méi)有任何引用鏈相連,或者用圖論的話來(lái)說(shuō)就是從GC Roots到這個(gè)對(duì)象不可達(dá)時(shí),則證明此對(duì)象不可能再被使用的。例如:通過(guò):Objcet 5,Object 6已經(jīng)Object 7三個(gè)對(duì)象,雖然互有關(guān)聯(lián),但是他們到GC Roots是不可達(dá)的,因此可以判定為是可以回收的對(duì)象。
在這里插入圖片描述

固定的可作為GC Roots對(duì)象包括如下幾種:

在虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象,譬如各個(gè)線程調(diào)用的方法堆棧中使用到的參數(shù),局部變量、臨時(shí)變量等
在方法區(qū)中類靜態(tài)屬性引用的對(duì)象,譬如Java類的引用類型靜態(tài)變量
在方法區(qū)中常量引用的對(duì)象,譬如字符串常量池里的引用
4.在本地方法棧中JNI(即通常所說(shuō)的Native方法)引用的對(duì)象
Java虛擬機(jī)內(nèi)部的引用,如基本數(shù)據(jù)類型對(duì)應(yīng)的Class對(duì)象,一些常駐的異常對(duì)象(比如 NullPointException、OutOfMemoryError)等,還有系統(tǒng)類加載器。
所有被同步鎖(synchronized關(guān)鍵字)持有的對(duì)象
7.反映Java虛擬機(jī)內(nèi)部情況的JMXBean、JVMTI中注冊(cè)的回調(diào)、本地代碼緩存等

引用的分類

引用分為如下四種:

強(qiáng)引用: 強(qiáng)引用是指在程序代碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關(guān)系。無(wú)論任何情況下,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象
軟引用: 軟引用是用來(lái)描述一些還有用,但非必須的對(duì)象。只被軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前,會(huì)被這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。
弱引用: 弱引用也是用來(lái)描述那些非必須對(duì)象,但是他的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生為止。當(dāng)垃圾收集器開(kāi)始工作,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。
虛引用: 一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的只是為了能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。

回收方法區(qū)

方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:廢棄的常量和不再使用的類型?;厥諒U棄常量
與回收J(rèn)ava堆中的對(duì)象非常類似。同時(shí)判斷一個(gè)常量是否廢棄還是相對(duì)簡(jiǎn)單,而要判斷一個(gè)類型是否屬于“不再被使用的類”的條件就比較苛刻了。需要同時(shí)滿足下面三個(gè)條件。

該類所有的實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生子類的實(shí)例。
加載該類的類加載器已經(jīng)被回收,這個(gè)通常很難達(dá)成
該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法。

分代收集理論

分代收集理論,實(shí)質(zhì)是一套符合大多數(shù)程序運(yùn)行實(shí)際情況的經(jīng)驗(yàn)法則,它建立在兩個(gè)分代假說(shuō)之上。

弱分代假說(shuō):絕大多數(shù)對(duì)象都是朝生夕滅的。
強(qiáng)分代假說(shuō):熬過(guò)越多次垃圾收集過(guò)程的對(duì)象就越難以消亡
收集器應(yīng)該將Java堆劃分出不同的區(qū)域,然后將回收對(duì)象依據(jù)其年齡(年齡即對(duì)象熬過(guò)垃圾收集過(guò)程的次數(shù))分配到不同的區(qū)域之中存儲(chǔ)。顯而易見(jiàn),如果一個(gè)區(qū)域中大多數(shù)對(duì)象都是朝生夕滅,難以熬過(guò)垃圾收集過(guò)程的話,那么把他們集中放在一起,每次回收時(shí)只關(guān)注如何保留少量存活而不是去標(biāo)記那些大量將要被回收的對(duì)象。就能以較低代價(jià)回收到大量的空間;如果剩下的都是難以消亡的對(duì)象,那把它們集中放在一塊,虛擬機(jī)便可以使用較低的頻率來(lái)回收這個(gè)區(qū)域。
新生代:每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,而每次回收后存活的少量對(duì)象,
將會(huì)逐步晉升到老年代中存放。
老年代:老年代里面的對(duì)象幾乎個(gè)個(gè)都是在 Survivor 區(qū)域中熬過(guò)來(lái)的,它們是不會(huì)那么容易就 “死掉” 了的。
跨代引用假說(shuō):跨代引用相對(duì)于同代引用來(lái)說(shuō)僅占極少數(shù)
只需要在新生代上建立一個(gè)全局的數(shù)據(jù)結(jié)構(gòu)(該結(jié)構(gòu)被稱為“記憶集”)這個(gè)結(jié)構(gòu)把老年代劃分為若干小塊,標(biāo)識(shí)出老年代的哪一塊內(nèi)存會(huì)存在跨代引用。此后當(dāng)發(fā)生Minor GC時(shí),只有包含了跨代引用的小塊內(nèi)存里的對(duì)象才會(huì)被加入到GC Roots 進(jìn)行掃描。

收集說(shuō)明
部分收集(Partial GC):

指目標(biāo)不是完整收集整個(gè)Java堆的垃圾收集。其中又分為:

新生代收集(Minor GC/Young GC):指目標(biāo)只是新生代的垃圾收集。

Minor GC觸發(fā)條件是:

  1. Eden區(qū)域滿了,或者新創(chuàng)建的對(duì)象大小>Eden所??臻g

  2. CMS設(shè)置了CMSScavengeBeforeRemark參數(shù),這樣在CMS的Remark之前會(huì)先做一次Minor GC來(lái)清理新生代,加速之后的Remark的速度,這樣整體的stop-the-world的時(shí)間反而短

  3. Full GC的時(shí)候也會(huì)先觸發(fā)Minor GC。執(zhí)行Minor GC需要注意:
    A:當(dāng)JVM無(wú)法為一個(gè)新的對(duì)象分配空間時(shí)會(huì)觸發(fā)Minor GC,比如當(dāng)Eden區(qū)滿了,所以分配頻率越高,越頻繁執(zhí)行Minor GC。


    老年代收集(Major GC/Old GC): 指目標(biāo)只是老年代的垃圾收集。目前只有CMS收集器會(huì)有單獨(dú)收集老年代的行為。
    混合收集(Mixed GC):指目標(biāo)是收集整個(gè)新生代以及部分老年代的垃圾收集。目前只有G1收集器會(huì)有這種行為。

整堆收集(Full GC):

收集整個(gè)Java堆和方法區(qū)的垃圾收集,包括新生代、老年代和永久代。Full GC因?yàn)樾枰獙?duì)整個(gè)堆進(jìn)行回收,所以比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC的次數(shù)。在對(duì)JVM調(diào)優(yōu)的過(guò)程中,很大一部分工作就是對(duì)Full GC的調(diào)節(jié),有如下原因可能導(dǎo)致Full GC:

當(dāng)老年代無(wú)法分配內(nèi)存的時(shí)候,會(huì)導(dǎo)致Minor GC, 當(dāng)發(fā)生Minor GC的時(shí)候可能會(huì)觸發(fā)Full GC,由于老年代要對(duì)新生代進(jìn)行擔(dān)保,由于進(jìn)行一次垃圾回收之前無(wú)法確定有多少對(duì)象存活,因此老年代并不清楚自己要擔(dān)保多少空間,因此采取用動(dòng)態(tài)估算的方法:也就是上一次回收發(fā)送是晉升到老年代的對(duì)象容量的平均值作為經(jīng)驗(yàn)值,這樣就會(huì)有一個(gè)問(wèn)題,當(dāng)發(fā)生一次Minor GC以后,存活的對(duì)象劇增(假設(shè)小對(duì)象),此時(shí)老年代并沒(méi)有滿,但是此時(shí)平均值增加了,會(huì)造成發(fā)生Full GC。
System.gc()被顯示調(diào)用。

面試題

  1. Java中什么樣的對(duì)象才能作為GC Roots,GC Roots有哪些呢?
    GC管理的主要區(qū)域是Java堆,一般情況下只針對(duì)堆進(jìn)行垃圾回收,方法區(qū)、棧和本地方法區(qū)不被GC所管理,因而選擇這些區(qū)域內(nèi)的對(duì)象作為GC roots,被GC roots引用的對(duì)象不被GC回收。
    GC Root
    常說(shuō)的GC roots,特指的是垃圾收集器(Garbage Collector)的對(duì)象,GC會(huì)收集那些不是GC Roots且沒(méi)有被GC roots引用的對(duì)象。
    一個(gè)對(duì)象可以屬于多個(gè)Root,GC Root有如下幾種:

    Class - 由系統(tǒng)類加載器(system class loader)加載的對(duì)象,這些類是不能被回收的。他們可以以靜態(tài)字段的方式保存持有其它對(duì)象,我們需要注意的一點(diǎn)就是,通過(guò)用戶自定義的類加載器的類,除非相應(yīng)的java.lang.Class實(shí)例以其它的某種(或多種)方式成為roots,否則它們并不是roots。
    Thread — 活著的線程
    Stack Local - Java方法的local變量或參數(shù)
    JNI Local - JNI方法的local變量或參數(shù)
    JNI Global - 全局JNI引用
    Monitor Used - 用于同步的監(jiān)控對(duì)象。
    內(nèi)存池/線程對(duì)象和線程快照對(duì)象
    String常量池
    2. 什么時(shí)候會(huì)觸發(fā)Full GC
    當(dāng)對(duì)象經(jīng)歷15次Minor GC后,在第16次Minor GC后,如果對(duì)象還存活則會(huì)被復(fù)制到老年代,在復(fù)制之前,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代,執(zhí)行這個(gè)判斷的原因是,JVM無(wú)法知道哪些對(duì)象是存活的,所以又相對(duì)粗淺的判斷。
    如果老年代有足夠的連續(xù)的內(nèi)存空間,則直接Minor GC,如果沒(méi)有足夠的空間,那么有兩種選擇,第一種,直接Minor GC,搏一搏,說(shuō)不定空間還夠,第二種,先對(duì)老年代進(jìn)行Full GC,騰出更多的空間用來(lái)Minor GC,至于執(zhí)行哪一種操作,關(guān)鍵看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。
    如果不允許擔(dān)保失敗的話,就會(huì)Full GC之后再進(jìn)行復(fù)制,如果允許擔(dān)保失敗。還不會(huì)直接Minor GC,還會(huì)在進(jìn)行一次評(píng)估,會(huì)繼續(xù)檢測(cè)老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小。如果大于的話,說(shuō)明直接Minor GC不出錯(cuò)的可能性大一些。
    通常情況下,還是會(huì)設(shè)置HandlePromotionFailure=true,Full GC能少一次就少一次。
    以下是邏輯總結(jié):
    發(fā)生Minor GC之前,虛擬機(jī)會(huì)檢測(cè):老年代最大可用的連續(xù)空間>新生代all 對(duì)象總空間?
    滿足,Minor GC是安全的,可以進(jìn)行Minor GC
    不滿足,虛擬機(jī)查看HandlePromotionFailure參數(shù):
    (1)為true,允許擔(dān)保失敗,會(huì)繼續(xù)檢測(cè)老年代最大可用的連續(xù)空間>歷次晉升到老年代對(duì)象的平均大小,若大于,將嘗試進(jìn)行一次Minor GC,若失敗,則重新進(jìn)行一次Full GC。
    (2)為false,則不允許冒險(xiǎn),要進(jìn)行Full GC(對(duì)老年代進(jìn)行GC)。




作者:碼農(nóng)飛哥
微信公眾號(hào):碼農(nóng)飛哥