溫馨提示×

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

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

JVM垃圾回收基本原理是什么

發(fā)布時(shí)間:2021-12-21 13:51:09 來(lái)源:億速云 閱讀:130 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“JVM垃圾回收基本原理是什么”,在日常操作中,相信很多人在JVM垃圾回收基本原理是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”JVM垃圾回收基本原理是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

JVM GC基本原理與GC算法

Java的內(nèi)存分配與回收全部由JVM垃圾回收進(jìn)程自動(dòng)完成。與C語(yǔ)言不同,Java開(kāi)發(fā)者不需要自己編寫(xiě)代碼實(shí)現(xiàn)垃圾回收。這是Java深受大家歡迎的眾多特性之一,能夠幫助程序員更好地編寫(xiě)Java程序。

Java關(guān)鍵術(shù)語(yǔ)

  • JavaAPI:一系列幫助開(kāi)發(fā)者創(chuàng)建Java應(yīng)用程序的封裝好的庫(kù)。

  • Java 開(kāi)發(fā)工具包 (JDK):一系列工具幫助開(kāi)發(fā)者創(chuàng)建Java應(yīng)用程序。JDK包含工具編譯、運(yùn)行、打包、分發(fā)和監(jiān)視Java應(yīng)用程序。

  • Java 虛擬機(jī)(JVM):JVM是一個(gè)抽象的計(jì)算機(jī)結(jié)構(gòu)。Java程序根據(jù)JVM的特性編寫(xiě)。JVM針對(duì)特定于操作系統(tǒng)并且可以將Java指令翻譯成底層系統(tǒng)的指令并執(zhí)行。JVM確保了Java的平臺(tái)無(wú)關(guān)性。

  • Java 運(yùn)行環(huán)境(JRE):JRE包含JVM實(shí)現(xiàn)和Java API。

Java HotSpot 虛擬機(jī)

每種JVM實(shí)現(xiàn)可能采用不同的方法實(shí)現(xiàn)垃圾回收機(jī)制。在收購(gòu)SUN之前,Oracle使用的是JRockit JVM,收購(gòu)之后使用HotSpot JVM。目前Oracle擁有兩種JVM實(shí)現(xiàn)并且一段時(shí)間后兩個(gè)JVM實(shí)現(xiàn)會(huì)合二為一。

HotSpot JVM是目前Oracle SE平臺(tái)標(biāo)準(zhǔn)核心組件的一部分。在這篇垃圾回收教程中,我們將會(huì)了解基于HotSpot虛擬機(jī)的垃圾回收原則。

Java堆內(nèi)存

我們有必要了解堆內(nèi)存在JVM內(nèi)存模型的角色。在運(yùn)行時(shí),Java的實(shí)例被存放在堆內(nèi)存區(qū)域。當(dāng)一個(gè)對(duì)象不再被引用時(shí),滿足條件就會(huì)從堆內(nèi)存移除。在垃圾回收進(jìn)程中,這些對(duì)象將會(huì)從堆內(nèi)存移除并且內(nèi)存空間被回收。堆內(nèi)存以下三個(gè)主要區(qū)域:

  1. 新生代(Young Generation)

    • Eden空間(Eden space,任何實(shí)例都通過(guò)Eden空間進(jìn)入運(yùn)行時(shí)內(nèi)存區(qū)域)

    • S0 Survivor空間(S0 Survivor space,存在時(shí)間長(zhǎng)的實(shí)例將會(huì)從Eden空間移動(dòng)到S0 Survivor空間)

    • S1 Survivor空間 (存在時(shí)間更長(zhǎng)的實(shí)例將會(huì)從S0 Survivor空間移動(dòng)到S1 Survivor空間)

  2. 老年代(Old Generation)實(shí)例將從S1提升到Tenured(終身代)

  3. 永久代(Permanent Generation)包含類、方法等細(xì)節(jié)的元信息

永久代空間 在Java SE8特性中已經(jīng)被移除。

Java 垃圾回收是一項(xiàng)自動(dòng)化的過(guò)程,用來(lái)管理程序所使用的運(yùn)行時(shí)內(nèi)存。通過(guò)這一自動(dòng)化過(guò)程,JVM 解除了程序員在程序中分配和釋放內(nèi)存資源的開(kāi)銷(xiāo)。

啟動(dòng)Java垃圾回收

作為一個(gè)自動(dòng)的過(guò)程,程序員不需要在代碼中顯示地啟動(dòng)垃圾回收過(guò)程。System.gc()Runtime.gc()用來(lái)請(qǐng)求JVM啟動(dòng)垃圾回收。

雖然這個(gè)請(qǐng)求機(jī)制提供給程序員一個(gè)啟動(dòng) GC 過(guò)程的機(jī)會(huì),但是啟動(dòng)由 JVM負(fù)責(zé)。JVM可以拒絕這個(gè)請(qǐng)求,所以并不保證這些調(diào)用都將執(zhí)行垃圾回收。啟動(dòng)時(shí)機(jī)的選擇由JVM決定,并且取決于堆內(nèi)存中Eden區(qū)是否可用。JVM將這個(gè)選擇留給了Java規(guī)范的實(shí)現(xiàn),不同實(shí)現(xiàn)具體使用的算法不盡相同。

毋庸置疑,我們知道垃圾回收過(guò)程是不能被強(qiáng)制執(zhí)行的。我剛剛發(fā)現(xiàn)了一個(gè)調(diào)用System.gc()有意義的場(chǎng)景。通過(guò)這篇文章了解一下 適合調(diào)用System.gc() 這種極端情況。

各種GC的觸發(fā)時(shí)機(jī)(When)

GC類型

說(shuō)到GC類型,就更有意思了,為什么呢,因?yàn)闃I(yè)界沒(méi)有統(tǒng)一的嚴(yán)格意義上的界限,也沒(méi)有嚴(yán)格意義上的GC類型,都是左邊一個(gè)教授一套名字,右邊一個(gè)作者一套名字。為什么會(huì)有這個(gè)情況呢,因?yàn)镚C類型是和收集器有關(guān)的,不同的收集器會(huì)有自己獨(dú)特的一些收集類型。所以作者在這里引用R大關(guān)于GC類型的介紹,作者覺(jué)得還是比較妥當(dāng)準(zhǔn)確的。如下:

  • Partial GC:并不收集整個(gè)GC堆的模式

    • Young GC(Minor GC):只收集young gen的GC

    • Old GC:只收集old gen的GC。只有CMS的concurrent collection是這個(gè)模式

    • Mixed GC:收集整個(gè)young gen以及部分old gen的GC。只有G1有這個(gè)模式

  • Full GC(Major GC):收集整個(gè)堆,包括young gen、old gen、perm gen(如果存在的話)等所有部分的模式。

觸發(fā)時(shí)機(jī)

上面大家也看到了,GC類型分分類是和收集器有關(guān)的,那么當(dāng)然了,對(duì)于不同的收集器,GC觸發(fā)時(shí)機(jī)也是不一樣的,作者就針對(duì)默認(rèn)的serial GC來(lái)說(shuō):

  • young GC:當(dāng)young gen中的eden區(qū)分配滿的時(shí)候觸發(fā)。注意young GC中有部分存活對(duì)象會(huì)晉升到old gen,所以young GC后old gen的占用量通常會(huì)有所升高。

  • full GC:當(dāng)準(zhǔn)備要觸發(fā)一次young GC時(shí),如果發(fā)現(xiàn)統(tǒng)計(jì)數(shù)據(jù)說(shuō)之前young GC的平均晉升大小比目前old gen剩余的空間大,則不會(huì)觸發(fā)young GC而是轉(zhuǎn)為觸發(fā)full GC(因?yàn)镠otSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都會(huì)同時(shí)收集整個(gè)GC堆,包括young gen,所以不需要事先觸發(fā)一次單獨(dú)的young GC);或者,如果有perm gen的話,要在perm gen分配空間但已經(jīng)沒(méi)有足夠空間時(shí),也要觸發(fā)一次full GC;或者System.gc()、heap dump帶GC,默認(rèn)也是觸發(fā)full GC。

FULL GC觸發(fā)條件詳解

除直接調(diào)用System.gc外,觸發(fā)Full GC執(zhí)行的情況有如下四種。

  1. 舊生代空間不足

舊生代空間只有在新生代對(duì)象轉(zhuǎn)入及創(chuàng)建為大對(duì)象、大數(shù)組時(shí)才會(huì)出現(xiàn)不足的現(xiàn)象,當(dāng)執(zhí)行Full GC后空間仍然不足,則拋出如下錯(cuò)誤:

java.lang.OutOfMemoryError: Java heap space

為避免以上兩種狀況引起的Full GC,調(diào)優(yōu)時(shí)應(yīng)盡量做到讓對(duì)象在Minor GC階段被回收、讓對(duì)象在新生代多存活一段時(shí)間及不要?jiǎng)?chuàng)建過(guò)大的對(duì)象及數(shù)組。

2. Permanet Generation空間滿

Permanet Generation中存放的為一些class的信息等,當(dāng)系統(tǒng)中要加載的類、反射的類和調(diào)用的方法較多時(shí),Permanet Generation可能會(huì)被占滿,在未配置為采用CMS GC的情況下會(huì)執(zhí)行Full GC。如果經(jīng)過(guò)Full GC仍然回收不了,那么JVM會(huì)拋出如下錯(cuò)誤信息:

java.lang.OutOfMemoryError: PermGen space

為避免Perm Gen占滿造成Full GC現(xiàn)象,可采用的方法為增大Perm Gen空間或轉(zhuǎn)為使用CMS GC。

3. CMS GC時(shí)出現(xiàn)promotion failed和concurrent mode failure

對(duì)于采用CMS進(jìn)行舊生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure兩種狀況,當(dāng)這兩種狀況出現(xiàn)時(shí)可能會(huì)觸發(fā)Full GC。

promotion failed是在進(jìn)行Minor GC時(shí),survivor space放不下、對(duì)象只能放入舊生代,而此時(shí)舊生代也放不下造成的;concurrent mode failure是在執(zhí)行CMS GC的過(guò)程中同時(shí)有對(duì)象要放入舊生代,而此時(shí)舊生代空間不足造成的。

應(yīng)對(duì)措施為:增大survivor space、舊生代空間或調(diào)低觸發(fā)并發(fā)GC的比率,但在JDK 5.0+、6.0+的版本中有可能會(huì)由于JDK的bug29導(dǎo)致CMS在remark完畢后很久才觸發(fā)sweeping動(dòng)作。對(duì)于這種狀況,可通過(guò)設(shè)置-XX: CMSMaxAbortablePrecleanTime=5(單位為ms)來(lái)避免。

  1. 統(tǒng)計(jì)得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩余空間

這是一個(gè)較為復(fù)雜的觸發(fā)情況,Hotspot為了避免由于新生代對(duì)象晉升到舊生代導(dǎo)致舊生代空間不足的現(xiàn)象,在進(jìn)行Minor GC時(shí),做了一個(gè)判斷,如果之前統(tǒng)計(jì)所得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩余空間,那么就直接觸發(fā)Full GC。

例如程序第一次觸發(fā)Minor GC后,有6MB的對(duì)象晉升到舊生代,那么當(dāng)下一次Minor GC發(fā)生時(shí),首先檢查舊生代的剩余空間是否大于6MB,如果小于6MB,則執(zhí)行Full GC。

當(dāng)新生代采用PS GC時(shí),方式稍有不同,PS GC是在Minor GC后也會(huì)檢查,例如上面的例子中第一次Minor GC后,PS GC會(huì)檢查此時(shí)舊生代的剩余空間是否大于6MB,如小于,則觸發(fā)對(duì)舊生代的回收。

除了以上4種狀況外,對(duì)于使用RMI來(lái)進(jìn)行RPC或管理的Sun JDK應(yīng)用而言,默認(rèn)情況下會(huì)一小時(shí)執(zhí)行一次Full GC。可通過(guò)在啟動(dòng)時(shí)通過(guò)- java -Dsun.rmi.dgc.client.gcInterval=3600000來(lái)設(shè)置Full GC執(zhí)行的間隔時(shí)間或通過(guò)-XX:+ DisableExplicitGC來(lái)禁止RMI調(diào)用System.gc。

總結(jié)

Minor GC ,F(xiàn)ull GC 觸發(fā)條件

Minor GC觸發(fā)條件:當(dāng)Eden區(qū)滿時(shí),觸發(fā)Minor GC。

Full GC觸發(fā)條件:

(1)調(diào)用System.gc時(shí),系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行

(2)老年代空間不足

(3)方法去空間不足

(4)通過(guò)Minor GC后進(jìn)入老年代的平均大小大于老年代的可用內(nèi)存

(5)由Eden區(qū)、From Space區(qū)向To Space區(qū)復(fù)制時(shí),對(duì)象大小大于To Space可用內(nèi)存,則把該對(duì)象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對(duì)象大小

什么是Stop the world

Java中Stop-The-World機(jī)制簡(jiǎn)稱STW,是在執(zhí)行垃圾收集算法時(shí),Java應(yīng)用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。Java中一種全局暫停現(xiàn)象,全局停頓,所有Java代碼停止,native代碼可以執(zhí)行,但不能與JVM交互;這些現(xiàn)象多半是由于gc引起。

GC時(shí)的Stop the World(STW)是大家最大的敵人。但可能很多人還不清楚,除了GC,JVM下還會(huì)發(fā)生停頓現(xiàn)象。

JVM里有一條特殊的線程--VM Threads,專門(mén)用來(lái)執(zhí)行一些特殊的VM Operation,比如分派GC,thread dump等,這些任務(wù),都需要整個(gè)Heap,以及所有線程的狀態(tài)是靜止的,一致的才能進(jìn)行。所以JVM引入了安全點(diǎn)(Safe Point)的概念,想辦法在需要進(jìn)行VM Operation時(shí),通知所有的線程進(jìn)入一個(gè)靜止的安全點(diǎn)。

除了GC,其他觸發(fā)安全點(diǎn)的VM Operation包括:

1. JIT相關(guān),比如Code deoptimization, Flushing code cache ;

2. Class redefinition (e.g. javaagent,AOP代碼植入的產(chǎn)生的instrumentation) ;

3. Biased lock revocation 取消偏向鎖 ;

4. Various debug operation (e.g. thread dump or deadlock check);

Java垃圾回收過(guò)程

垃圾回收是一種回收無(wú)用內(nèi)存空間并使其對(duì)未來(lái)實(shí)例可用的過(guò)程。

Eden 區(qū):當(dāng)一個(gè)實(shí)例被創(chuàng)建了,首先會(huì)被存儲(chǔ)在堆內(nèi)存年輕代的 Eden 區(qū)中。

注意:如果你不能理解這些詞匯,我建議你閱讀這篇 垃圾回收介紹 ,這篇教程詳細(xì)地介紹了內(nèi)存模型、JVM 架構(gòu)以及這些術(shù)語(yǔ)。

Survivor 區(qū)(S0 和 S1):作為年輕代 GC(Minor GC)周期的一部分,存活的對(duì)象(仍然被引用的)從 Eden 區(qū)被移動(dòng)到 Survivor 區(qū)的 S0 中。類似的,垃圾回收器會(huì)掃描 S0 然后將存活的實(shí)例移動(dòng)到 S1 中。

(譯注:此處不應(yīng)該是Eden和S0中存活的都移到S1么,為什么會(huì)先移到S0再?gòu)腟0移到S1?)

死亡的實(shí)例(不再被引用)被標(biāo)記為垃圾回收。根據(jù)垃圾回收器(有四種常用的垃圾回收器,將在下一教程中介紹它們)選擇的不同,要么被標(biāo)記的實(shí)例都會(huì)不停地從內(nèi)存中移除,要么回收過(guò)程會(huì)在一個(gè)單獨(dú)的進(jìn)程中完成。

老年代: 老年代(Old or tenured generation)是堆內(nèi)存中的第二塊邏輯區(qū)。當(dāng)垃圾回收器執(zhí)行 Minor GC 周期時(shí),在 S1 Survivor 區(qū)中的存活實(shí)例將會(huì)被晉升到老年代,而未被引用的對(duì)象被標(biāo)記為回收。

老年代 GC(Major GC):相對(duì)于 Java 垃圾回收過(guò)程,老年代是實(shí)例生命周期的最后階段。Major GC 掃描老年代的垃圾回收過(guò)程。如果實(shí)例不再被引用,那么它們會(huì)被標(biāo)記為回收,否則它們會(huì)繼續(xù)留在老年代中。

內(nèi)存碎片:一旦實(shí)例從堆內(nèi)存中被刪除,其位置就會(huì)變空并且可用于未來(lái)實(shí)例的分配。這些空出的空間將會(huì)使整個(gè)內(nèi)存區(qū)域碎片化。為了實(shí)例的快速分配,需要進(jìn)行碎片整理。基于垃圾回收器的不同選擇,回收的內(nèi)存區(qū)域要么被不停地被整理,要么在一個(gè)單獨(dú)的GC進(jìn)程中完成。

垃圾回收中實(shí)例的終結(jié)

在釋放一個(gè)實(shí)例和回收內(nèi)存空間之前,Java 垃圾回收器會(huì)調(diào)用實(shí)例各自的 finalize() 方法,從而該實(shí)例有機(jī)會(huì)釋放所持有的資源。雖然可以保證 finalize() 會(huì)在回收內(nèi)存空間之前被調(diào)用,但是沒(méi)有指定的順序和時(shí)間。多個(gè)實(shí)例間的順序是無(wú)法被預(yù)知,甚至可能會(huì)并行發(fā)生。程序不應(yīng)該預(yù)先調(diào)整實(shí)例之間的順序并使用 finalize() 方法回收資源。

  • 任何在 finalize過(guò)程中未被捕獲的異常會(huì)自動(dòng)被忽略,然后該實(shí)例的 finalize 過(guò)程被取消。

  • JVM 規(guī)范中并沒(méi)有討論關(guān)于弱引用的垃圾回收機(jī)制,也沒(méi)有很明確的要求。具體的實(shí)現(xiàn)都由實(shí)現(xiàn)方?jīng)Q定。

  • 垃圾回收是由一個(gè)守護(hù)線程完成的。

對(duì)象什么時(shí)候符合垃圾回收的條件?

  • 所有實(shí)例都沒(méi)有活動(dòng)線程訪問(wèn)。

  • 沒(méi)有被其他任何實(shí)例訪問(wèn)的循環(huán)引用實(shí)例。

Java 中有不同的引用類型。判斷實(shí)例是否符合垃圾收集的條件都依賴于它的引用類型。

引用類型垃圾收集
強(qiáng)引用(Strong Reference)不符合垃圾收集
軟引用(Soft Reference)垃圾收集可能會(huì)執(zhí)行,但會(huì)作為最后的選擇
弱引用(Weak Reference)符合垃圾收集
虛引用(Phantom Reference)符合垃圾收集

在編譯過(guò)程中作為一種優(yōu)化技術(shù),Java 編譯器能選擇給實(shí)例賦 null 值,從而標(biāo)記實(shí)例為可回收。

class Animal {
    public static void main(String[] args) {
        Animal lion = new Animal();
        System.out.println("Main is completed.");
    }
    protected void finalize() {
        System.out.println("Rest in Peace!");
    }
}

在上面的類中,lion 對(duì)象在實(shí)例化行后從未被使用過(guò)。因此 Java 編譯器作為一種優(yōu)化措施可以直接在實(shí)例化行后賦值lion = null。因此,即使在 SOP 輸出之前, finalize 函數(shù)也能夠打印出 'Rest in Peace!'。我們不能證明這確定會(huì)發(fā)生,因?yàn)樗蕾嘕VM的實(shí)現(xiàn)方式和運(yùn)行時(shí)使用的內(nèi)存。然而,我們還能學(xué)習(xí)到一點(diǎn):如果編譯器看到該實(shí)例在未來(lái)再也不會(huì)被引用,能夠選擇并提早釋放實(shí)例空間。

  • 關(guān)于對(duì)象什么時(shí)候符合垃圾回收有一個(gè)更好的例子。實(shí)例的所有屬性能被存儲(chǔ)在寄存器中,隨后寄存器將被訪問(wèn)并讀取內(nèi)容。無(wú)一例外,這些值將被寫(xiě)回到實(shí)例中。雖然這些值在將來(lái)能被使用,這個(gè)實(shí)例仍然能被標(biāo)記為符合垃圾回收。這是一個(gè)很經(jīng)典的例子,不是嗎?

  • 當(dāng)被賦值為null時(shí),這是很簡(jiǎn)單的一個(gè)符合垃圾回收的示例。當(dāng)然,復(fù)雜的情況可以像上面的幾點(diǎn)。這是由 JVM 實(shí)現(xiàn)者所做的選擇。目的是留下盡可能小的內(nèi)存占用,加快響應(yīng)速度,提高吞吐量。為了實(shí)現(xiàn)這一目標(biāo), JVM 的實(shí)現(xiàn)者可以選擇一個(gè)更好的方案或算法在垃圾回收過(guò)程中回收內(nèi)存空間。

  • 當(dāng) finalize() 方法被調(diào)用時(shí),JVM 會(huì)釋放該線程上的所有同步鎖。

GC Scope 示例程序

Class GCScope {
    GCScope t;
    static int i = 1;
    public static void main(String args[]) {
        GCScope t1 = new GCScope();
        GCScope t2 = new GCScope();
        GCScope t3 = new GCScope();
        // No Object Is Eligible for GC
        t1.t = t2; // No Object Is Eligible for GC
        t2.t = t3; // No Object Is Eligible for GC
        t3.t = t1; // No Object Is Eligible for GC
        t1 = null;
        // No Object Is Eligible for GC (t3.t still has a reference to t1)
        t2 = null;
        // No Object Is Eligible for GC (t3.t.t still has a reference to t2)
        t3 = null;
        // All the 3 Object Is Eligible for GC (None of them have a reference.
        // only the variable t of the objects are referring each other in a
        // rounded fashion forming the Island of objects with out any external
        // reference)
    }
    protected void finalize() {
        System.out.println("Garbage collected from object" + i);
        i++;
    }
class GCScope {
    GCScope t;
    static int i = 1;
    public static void main(String args[]) {
        GCScope t1 = new GCScope();
        GCScope t2 = new GCScope();
        GCScope t3 = new GCScope();
        // 沒(méi)有對(duì)象符合GC
        t1.t = t2; // 沒(méi)有對(duì)象符合GC
        t2.t = t3; // 沒(méi)有對(duì)象符合GC
        t3.t = t1; // 沒(méi)有對(duì)象符合GC
        t1 = null;
        // 沒(méi)有對(duì)象符合GC (t3.t 仍然有一個(gè)到 t1 的引用)
        t2 = null;
        // 沒(méi)有對(duì)象符合GC (t3.t.t 仍然有一個(gè)到 t2 的引用)
        t3 = null;
        // 所有三個(gè)對(duì)象都符合GC (它們中沒(méi)有一個(gè)擁有引用。
        // 只有各對(duì)象的變量 t 還指向了彼此,
        // 形成了一個(gè)由對(duì)象組成的環(huán)形的島,而沒(méi)有任何外部的引用。)
    }
    protected void finalize() {
        System.out.println("Garbage collected from object" + i);
        i++;
    }

JVM GC算法

在判斷哪些內(nèi)存需要回收和什么時(shí)候回收用到GC 算法,本文主要對(duì)GC 算法進(jìn)行講解。

JVM垃圾判定算法

常見(jiàn)的JVM垃圾判定算法包括:引用計(jì)數(shù)算法、可達(dá)性分析算法。

引用計(jì)數(shù)算法(Reference Counting)

引用計(jì)數(shù)算法是通過(guò)判斷對(duì)象的引用數(shù)量來(lái)決定對(duì)象是否可以被回收。

給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。

優(yōu)點(diǎn):簡(jiǎn)單,高效,現(xiàn)在的objective-c用的就是這種算法。

缺點(diǎn):很難處理循環(huán)引用,相互引用的兩個(gè)對(duì)象則無(wú)法釋放。因此目前主流的Java虛擬機(jī)都摒棄掉了這種算法。

舉個(gè)簡(jiǎn)單的例子,對(duì)象objA和objB都有字段instance,賦值令objA.instance=objB及objB.instance=objA,除此之外,這兩個(gè)對(duì)象沒(méi)有任何引用,實(shí)際上這兩個(gè)對(duì)象已經(jīng)不可能再被訪問(wèn),但是因?yàn)榛ハ嘁茫瑢?dǎo)致它們的引用計(jì)數(shù)都不為0,因此引用計(jì)數(shù)算法無(wú)法通知GC收集器回收它們。

public class ReferenceCountingGC {
    public Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        System.gc();//GC
    }
}

運(yùn)行結(jié)果

[GC (System.gc()) [PSYoungGen: 3329K->744K(38400K)] 3329K->752K(125952K), 0.0341414 secs] [Times: user=0.00 sys=0.00, real=0.06 secs] 
[Full GC (System.gc()) [PSYoungGen: 744K->0K(38400K)] [ParOldGen: 8K->628K(87552K)] 752K->628K(125952K), [Metaspace: 3450K->3450K(1056768K)], 0.0060728 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 38400K, used 998K [0x00000000d5c00000, 0x00000000d8680000, 0x0000000100000000)
  eden space 33280K, 3% used [0x00000000d5c00000,0x00000000d5cf9b20,0x00000000d7c80000)
  from space 5120K, 0% used [0x00000000d7c80000,0x00000000d7c80000,0x00000000d8180000)
  to   space 5120K, 0% used [0x00000000d8180000,0x00000000d8180000,0x00000000d8680000)
 ParOldGen       total 87552K, used 628K [0x0000000081400000, 0x0000000086980000, 0x00000000d5c00000)
  object space 87552K, 0% used [0x0000000081400000,0x000000008149d2c8,0x0000000086980000)
 Metaspace       used 3469K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0

從運(yùn)行結(jié)果看,GC日志中包含“3329K->744K”,意味著虛擬機(jī)并沒(méi)有因?yàn)檫@兩個(gè)對(duì)象互相引用就不回收它們,說(shuō)明虛擬機(jī)不是通過(guò)引用技術(shù)算法來(lái)判斷對(duì)象是否存活的。

可達(dá)性分析算法(根搜索算法)

可達(dá)性分析算法是通過(guò)判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá)來(lái)決定對(duì)象是否可以被回收。

從GC Roots(每種具體實(shí)現(xiàn)對(duì)GC Roots有不同的定義)作為起點(diǎn),向下搜索它們引用的對(duì)象,可以生成一棵引用樹(shù),樹(shù)的節(jié)點(diǎn)視為可達(dá)對(duì)象,反之視為不可達(dá)。

JVM垃圾回收基本原理是什么

在Java語(yǔ)言中,可以作為GC Roots的對(duì)象包括下面幾種:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中的引用對(duì)象。

  • 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象。

  • 方法區(qū)中的常量引用的對(duì)象。

  • 本地方法棧中JNI(Native方法)的引用對(duì)象

真正標(biāo)記以為對(duì)象為可回收狀態(tài)至少要標(biāo)記兩次。

四種引用

強(qiáng)引用就是指在程序代碼之中普遍存在的,類似”O(jiān)bject obj = new Object()”這類的引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。

Object obj = new Object();

軟引用是用來(lái)描述一些還有用但并非必需的對(duì)象,對(duì)于軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列進(jìn)回收范圍進(jìn)行第二次回收。如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。在JDK1.2之后,提供了SoftReference類來(lái)實(shí)現(xiàn)軟引用。

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);

弱引用也是用來(lái)描述非必需對(duì)象的,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象,只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時(shí),無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。在JDK1.2之后,提供了WeakReference類來(lái)實(shí)現(xiàn)弱引用。

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);

虛引用也成為幽靈引用或者幻影引用,它是最弱的一中引用關(guān)系。一個(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)通知。在JDK1.2之后,提供給了PhantomReference類來(lái)實(shí)現(xiàn)虛引用。

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);

JVM垃圾回收算法

常見(jiàn)的垃圾回收算法包括:標(biāo)記-清除算法,復(fù)制算法,標(biāo)記-整理算法,分代收集算法。

在介紹JVM垃圾回收算法前,先介紹一個(gè)概念。

Stop-the-World

Stop-the-world意味著 JVM由于要執(zhí)行GC而停止了應(yīng)用程序的執(zhí)行,并且這種情形會(huì)在任何一種GC算法中發(fā)生。當(dāng)Stop-the-world發(fā)生時(shí),除了GC所需的線程以外,所有線程都處于等待狀態(tài)直到GC任務(wù)完成。事實(shí)上,GC優(yōu)化很多時(shí)候就是指減少Stop-the-world發(fā)生的時(shí)間,從而使系統(tǒng)具有高吞吐 、低停頓的特點(diǎn)。

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

之所以說(shuō)標(biāo)記/清除算法是幾種GC算法中最基礎(chǔ)的算法,是因?yàn)楹罄m(xù)的收集算法都是基于這種思路并對(duì)其不足進(jìn)行改進(jìn)而得到的。標(biāo)記/清除算法的基本思想就跟它的名字一樣,分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。

標(biāo)記階段:標(biāo)記的過(guò)程其實(shí)就是前面介紹的可達(dá)性分析算法的過(guò)程,遍歷所有的GC Roots對(duì)象,對(duì)從GC Roots對(duì)象可達(dá)的對(duì)象都打上一個(gè)標(biāo)識(shí),一般是在對(duì)象的header中,將其記錄為可達(dá)對(duì)象;

清除階段:清除的過(guò)程是對(duì)堆內(nèi)存進(jìn)行遍歷,如果發(fā)現(xiàn)某個(gè)對(duì)象沒(méi)有被標(biāo)記為可達(dá)對(duì)象(通過(guò)讀取對(duì)象header信息),則將其回收。

不足:

  • 標(biāo)記和清除過(guò)程效率都不高

  • 會(huì)產(chǎn)生大量碎片,內(nèi)存碎片過(guò)多可能導(dǎo)致無(wú)法給大對(duì)象分配內(nèi)存。

JVM垃圾回收基本原理是什么

復(fù)制算法(Copying)

將內(nèi)存劃分為大小相等的兩塊,每次只使用其中一塊,當(dāng)這一塊內(nèi)存用完了就將還存活的對(duì)象復(fù)制到另一塊上面,然后再把使用過(guò)的內(nèi)存空間進(jìn)行一次清理。

現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集算法來(lái)回收新生代,但是并不是將內(nèi)存劃分為大小相等的兩塊,而是分為一塊較大的 Eden 空間和兩塊較小的 Survior 空間,每次使用 Eden 空間和其中一塊 Survivor。在回收時(shí),將 Eden 和 Survivor 中還存活著的對(duì)象一次性復(fù)制到另一塊 Survivor 空間上,最后清理 Eden 和 使用過(guò)的那一塊 Survivor。HotSpot 虛擬機(jī)的 Eden 和 Survivor 的大小比例默認(rèn)為 8:1,保證了內(nèi)存的利用率達(dá)到 90 %。如果每次回收有多于 10% 的對(duì)象存活,那么一塊 Survivor 空間就不夠用了,此時(shí)需要依賴于老年代進(jìn)行分配擔(dān)保,也就是借用老年代的空間。

不足:

  • 將內(nèi)存縮小為原來(lái)的一半,浪費(fèi)了一半的內(nèi)存空間,代價(jià)太高;如果不想浪費(fèi)一半的空間,就需要有額外的空間進(jìn)行分配擔(dān)保,以應(yīng)對(duì)被使用的內(nèi)存中所有對(duì)象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。

  • 復(fù)制收集算法在對(duì)象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作,效率將會(huì)變低。

JVM垃圾回收基本原理是什么

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

標(biāo)記—整理算法和標(biāo)記—清除算法一樣,但是標(biāo)記—整理算法不是把存活對(duì)象復(fù)制到另一塊內(nèi)存,而是把存活對(duì)象往內(nèi)存的一端移動(dòng),然后直接回收邊界以外的內(nèi)存,因此其不會(huì)產(chǎn)生內(nèi)存碎片。標(biāo)記—整理算法提高了內(nèi)存的利用率,并且它適合在收集對(duì)象存活時(shí)間較長(zhǎng)的老年代。

不足:

效率不高,不僅要標(biāo)記存活對(duì)象,還要整理所有存活對(duì)象的引用地址,在效率上不如復(fù)制算法。

JVM垃圾回收基本原理是什么

分代收集算法(Generational Collection)

分代回收算法實(shí)際上是把復(fù)制算法和標(biāo)記整理法的結(jié)合,并不是真正一個(gè)新的算法,一般分為:老年代(Old Generation)和新生代(Young Generation),老年代就是很少垃圾需要進(jìn)行回收的,新生代就是有很多的內(nèi)存空間需要回收,所以不同代就采用不同的回收算法,以此來(lái)達(dá)到高效的回收算法。

新生代:由于新生代產(chǎn)生很多臨時(shí)對(duì)象,大量對(duì)象需要進(jìn)行回收,所以采用復(fù)制算法是最高效的。

老年代:回收的對(duì)象很少,都是經(jīng)過(guò)幾次標(biāo)記后都不是可回收的狀態(tài)轉(zhuǎn)移到老年代的,所以僅有少量對(duì)象需要回收,故采用標(biāo)記清除或者標(biāo)記整理算法。

到此,關(guān)于“JVM垃圾回收基本原理是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

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

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

jvm
AI