溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JVM中垃圾收集算法的示例分析

發(fā)布時間:2021-06-04 10:16:26 來源:億速云 閱讀:129 作者:小新 欄目:開發(fā)技術(shù)

這篇文章給大家分享的是有關(guān)JVM中垃圾收集算法的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

一、什么是垃圾

本文要講的是垃圾收集算法,那么首先要確定的問題就是什么是垃圾,也就是哪些對象是要被回收的,對此有兩種判斷方式:

1.1 引用計數(shù)算法

什么樣的對象是要被回收的,很明顯,沒有被引用的對象才要被回收。因此在對象中加一個引用計數(shù)器,當(dāng)有一個對象引用該對象的時候,計數(shù)器就加一,當(dāng)引用結(jié)束后,計數(shù)器就減一,當(dāng)計數(shù)器為0的時候,對象就可以被回收了。

1.1.1 優(yōu)點
  • 原理簡單

  • 判斷效率高

1.1.2 缺點
  • 需要花費額外的內(nèi)存空間(引用計數(shù)器)

  • 無法回收相互循環(huán)引用的對象:比如有對象A和對象B,A引用B,B引用A,兩對象的引用計數(shù)器都為1,理論上來說,沒有其他對象能夠引用到A和B了,因此這兩個對象應(yīng)該被回收,然后按照引用計數(shù)算法的判斷,這兩個對象無法被回收。(要克服這個缺點,需要在代碼中做很多特殊處理)

1.2 可達(dá)性分析算法

因為引用計數(shù)算法的缺陷,各大主流的商用程序語言都采用可達(dá)性分析算法來判斷對象是否需要被回收??蛇_(dá)性顧名思義,是指對象跟對象之間有引用關(guān)系,此處有兩種引用關(guān)系:

  • 對象A引用對象B,則稱對象A到對象B可達(dá)

  • 對象A引用對象B,對象B引用對象C,對象A可以通過若干個對象(此處為對象B)引用到對象C,則稱對象A到對象C可達(dá)。

要判斷一個對象是否可達(dá),首先要有一個根對象,在Java中有一系列被稱為“GC Roots”的根對象作為起始節(jié)點集,任何從“GC Roots”不可達(dá)的對象都是需要被垃圾收集器回收的垃圾。

1.2.1 優(yōu)點

可以有效解決引用計數(shù)算法的相互循環(huán)引用問題

 二、什么是引用

在討論什么是垃圾的時候,多次提到引用一詞,那么什么是引用呢?

2.1 JDK1.2以前

按照書中的說法,在JDK1.2以前,引用的意思是:如果reference類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱該reference數(shù)據(jù)是代表某塊內(nèi)存、某個對象的引用。
在這種定義下可以發(fā)現(xiàn),對于一個對象來說,就只有未被引用和被引用兩種狀態(tài)了,但其實可以發(fā)現(xiàn),在實際應(yīng)用中,并不是一定要把對象回收掉的,書中有個詞就很貼切,“食之無味,棄之可惜”,我們想要的是當(dāng)內(nèi)存空間足夠的時候,把這部分本該回收的對象留著不回收,當(dāng)內(nèi)存不夠的時候,就將其回收。

2.2 JDK1.2之后

因此在JDK1.2之后,引用的概念就擴(kuò)張到了以下四種:

  • 強(qiáng)引用:指傳統(tǒng)意義上的引用,有強(qiáng)引用的對象是肯定不被回收的;

  • 軟引用:用于描述一些還有用,但非必須的對象,當(dāng)要發(fā)生內(nèi)存溢出的時候,就會回收軟引用對象;

  • 弱引用:用于描述非必須對象,強(qiáng)度比軟引用弱一點,當(dāng)垃圾收集器開始工作,無論內(nèi)存夠不夠都會回收弱引用對象;

  • 虛引用:虛引用意思就是這個引用跟沒有一樣,對對象完全沒有印象,其存在的唯一作用就是在對象被垃圾收集器回收時能收到一個系統(tǒng)通知。

三、垃圾判斷全流程

按照書中所述,我畫了個流程圖,如下:

JVM中垃圾收集算法的示例分析

一個對象在被回收前,需要進(jìn)行兩次標(biāo)記,第一次進(jìn)行可達(dá)性分析后,對象被垃圾收集器認(rèn)為是垃圾,則對對象進(jìn)行第一次標(biāo)記,然后垃圾收集器會給予對象一次自救的機(jī)會,不然就沒必要兩次標(biāo)記了,一次標(biāo)記直接回收就好了。
我們都知道對象有個finalize()方法,自救的機(jī)會就在這個方法中,當(dāng)?shù)谝淮螛?biāo)記后,垃圾收集器會對對象做一次篩選,篩選條件是要不要執(zhí)行對象的finalize()方法,如果開發(fā)者未對finalize()方法進(jìn)行覆寫或者虛擬機(jī)已經(jīng)執(zhí)行過該對象的finalize()方法了,那么自然就不用再執(zhí)行了,反之則需要執(zhí)行。
將篩選出來的需要執(zhí)行finalize()方法的對象放入一個特定的隊列中,由虛擬機(jī)統(tǒng)一執(zhí)行,如果finalize()方法中使得對象被別的對象引用了,導(dǎo)致可達(dá)性分析認(rèn)為對象是可用的,那么自救就成功了。
根據(jù)篩選的條件可以知道,對象的自救機(jī)會在整個程序中只有一次,因為finalize()方法只會被執(zhí)行一次。
需要注意的是,官方明確申明不推薦使用finalize()方法,因為使用它的不確定性太大。對于資源清理等操作,try…catch語法可以做的更好。

四、垃圾收集算法

大多數(shù)虛擬機(jī)的垃圾收集都采用了分代收集的形式,這是因為三條經(jīng)驗法則:

  • 弱分代假說:絕大多數(shù)對象都是朝生夕死的;

  • 強(qiáng)分代假說:熬過越多次垃圾收集過程的對象就越難以消亡

  • 跨代引用假說:跨代引用相對于同代引用來說僅占極少數(shù)

因為對象的生存周期是不一樣的,所以我們不能對所有對象采用同一種垃圾收集算法,采用分代收集,將有共性的對象放在一個集合里,會大大地提高垃圾收集效率。
按照上述經(jīng)驗法則,可以將堆內(nèi)存分為兩代:

  • 新生代:對應(yīng)弱分代假說

  • 老年代:對應(yīng)強(qiáng)分代假說

下面分別介紹三種垃圾收集算法:

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

標(biāo)記-清除算法是最基礎(chǔ)的垃圾收集算法,顧名思義,標(biāo)記就是判斷對象是否是垃圾,也就是前面第四節(jié)講到的內(nèi)容,清除就是統(tǒng)一回收垃圾,該算法有兩種執(zhí)行過程:

  • 標(biāo)記所有需要被回收的對象,統(tǒng)一回收所有標(biāo)記的對象;

  • 標(biāo)記所有存活對象,統(tǒng)一回收所有未被標(biāo)記的對象。

標(biāo)記-清除算法示意圖:

JVM中垃圾收集算法的示例分析

標(biāo)記-清除算法有兩大缺點:

  • 執(zhí)行效率不穩(wěn)定:標(biāo)記和清除兩個過程的執(zhí)行效率隨對象數(shù)量的增加而降低

  • 內(nèi)存空間碎片化:執(zhí)行完標(biāo)記和清除后,會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片,當(dāng)分配大對象的時候,如果找不到足夠的連續(xù)內(nèi)存,那么會提前觸發(fā)下一次垃圾收集。

4.2 標(biāo)記-復(fù)制算法

基于標(biāo)記-清除算法的缺點,標(biāo)記-復(fù)制算法將內(nèi)存空間一分為二,兩塊內(nèi)存空間等大,每次只使用其中一塊內(nèi)存空間,當(dāng)這一塊內(nèi)存空間用完了,就把存活的對象復(fù)制到另一塊內(nèi)存空間中,然后一次性清理所有已使用的內(nèi)存空間。

標(biāo)記-復(fù)制算法示意圖:

JVM中垃圾收集算法的示例分析

標(biāo)記-復(fù)制算法解決了標(biāo)記-清除算法面對大量可回收對象場景下的不足之處,面對這種情況,標(biāo)記-復(fù)制算法只需要將內(nèi)存空間中的存活對象復(fù)制到另一半內(nèi)存空間中,可以有效解決內(nèi)存碎片的問題,在給對象分配內(nèi)存的時候,只需要移動堆頂指針按順序分配即可,不過這個算法也有缺點:

  • 面對大量不可回收對象的時候,會產(chǎn)生大量內(nèi)存間對象復(fù)制的開銷;

  • 原先的內(nèi)存空間縮小了一半,會造成嚴(yán)重的空間浪費

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

標(biāo)記-復(fù)制算法不足以應(yīng)對有大量存活對象的場景,因此就有了標(biāo)記-整理算法,該算法的執(zhí)行流程如下:

  • 與其他算法一樣,首先對對象進(jìn)行標(biāo)記;

  • 將所有存活對象往內(nèi)存的一個方向移動;

  • 直接清理掉邊界以外的內(nèi)存

標(biāo)記-整理算法示意圖:

JVM中垃圾收集算法的示例分析

標(biāo)記-整理算法同樣可以解決內(nèi)存碎片化問題,并且不會造成空間浪費,不過它也有缺點:

在大量對象存活的情況下,移動對象并更新引用也會花費大量時間

4.4 應(yīng)用

不同的場景適用不同的垃圾收集算法,像標(biāo)記-復(fù)制算法就適用于存活對象少的情況下,也就是新生代區(qū)域,像標(biāo)記-整理算法就適用于存活對象多的情況下,也就是老年代。
這里有點需要注意的是,標(biāo)記-整理算法對于老年代來說也不是完美的,在5.3節(jié)我們說過,在大量對象存活的情況下,移動對象和更新引用也是要花費大量時間的,不過算法這個東西吧,它比的是誰更適合,對于標(biāo)記-復(fù)制算法來說,我把區(qū)域一分為二,如果大量對象存活,我要把對象全部復(fù)制到另一塊內(nèi)存區(qū)域,這個開銷不見得比標(biāo)記-整理算法少,并且它還有個缺點就是可用內(nèi)存一下子少了一半,這個問題在標(biāo)記-整理算法中是沒有的。也有的虛擬機(jī)采用標(biāo)記-清除算法標(biāo)記-整理算法協(xié)作的垃圾收集方案,沒有最適合,只有更適合。

4.5 優(yōu)化

前面講標(biāo)記-復(fù)制算法的時候說到要把內(nèi)存區(qū)域等半分,這是在沒有規(guī)定場景的情況下,在新生代中采用該垃圾收集算法可以做更好的優(yōu)化。

眾所周知,新生代中的對象都是朝生夕死的,因此當(dāng)標(biāo)記完成后的存活對象肯定是少量的,根據(jù)這個現(xiàn)象,可以將內(nèi)存區(qū)域非等半分,比如說9:1的分法,這里我們將90%的內(nèi)存區(qū)域稱為Eden空間,將10%的內(nèi)存區(qū)域稱為Survivor空間,一開始使用Eden空間的內(nèi)存,當(dāng)垃圾收集時,將Eden空間的存活對象復(fù)制到Survivor空間中。
這里肯定有人要問了,那下一次使用Survivor空間不是就只有10%的內(nèi)存了嗎?

對的,所以這里有兩種解決方案:

  • 將存活對象從Eden空間復(fù)制到Survivor空間后,再從Survivor空間復(fù)制回Eden空間;

  • Eden空間再分離出一個Survivor空間,每次可使用的內(nèi)存為一個Eden空間一個Survivor空間,當(dāng)垃圾收集時,將使用的內(nèi)存區(qū)域中的存活對象復(fù)制到另一個Survivor空間中,下一次的可用內(nèi)存則為Eden空間和這個Survivor空間,如此循環(huán)往復(fù)。

第二種方法就是大名鼎鼎的半?yún)^(qū)復(fù)制分代策略,現(xiàn)在叫Appel式回收,因為提出這個策略的人叫Apple,目前很多虛擬機(jī)在新生代的垃圾收集算法中采用這個策略。

4.5.1 缺點

半?yún)^(qū)復(fù)制分代策略也是有缺點的,從上面的敘述中我們可以知道,Eden空間Survivor空間的內(nèi)存占比為8:1:1,如果當(dāng)垃圾收集后的存活對象所需要的內(nèi)存空間大于一個Survivor空間時,那就難辦了。

4.5.2 補(bǔ)丁

既然Survivor空間的內(nèi)存不夠放存活對象了,那就去借內(nèi)存區(qū)域,這個借當(dāng)然不能跟Eden空間Survivor空間借,不然會影響到整個算法,增加算法的復(fù)雜度。新生代不能借,那就跟老年代借,這里就有一個所謂的內(nèi)存分配擔(dān)保,放不下的存活對象將直接通過分配擔(dān)保機(jī)制進(jìn)入到老年代中。有了這個“逃生門”一樣的設(shè)計,這個策略才算是沒有漏洞。

五、寫在后面

幾個垃圾收集算法的圖是我直接截了書里面的圖,因為我覺得它講的很詳細(xì)了,第四節(jié)垃圾判斷過程在書中實際上是一長串的代碼,看懂不難,不過我想畫個流程圖可能更清楚點,這個流程圖是用plantUML畫出來的,這個工具可以用代碼畫出各種圖,功能強(qiáng)大,有興趣的可以百度搜搜,下面是這個流程圖的代碼:

@startuml
start
:對對象進(jìn)行可達(dá)性分析;
if (對象是否為垃圾?) then (是)
    :進(jìn)行第一次標(biāo)記;
    if (對象沒有覆蓋finalize()方法 或 finalize()方法已經(jīng)被虛擬機(jī)調(diào)用) then(是)
        :沒必要執(zhí)行對象的finalize()方法;
    else (否)
        :將對象放入隊列F-Queue中;
        :等待虛擬機(jī)的Finalizer線程執(zhí)行對象的finalize()方法;
        :執(zhí)行對象的finalize()方法;
    endif
    if (對象是否為垃圾?) then (是)
        :進(jìn)行第二次標(biāo)記;
        :垃圾回收;
    else (否)
        stop
    endif
else (否)
    stop
endif
stop
@enduml

感謝各位的閱讀!關(guān)于“JVM中垃圾收集算法的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

jvm
AI