JVM垃圾回收機(jī)制

Java開發(fā)有個(gè)很基礎(chǔ)的問題,雖然我們平時(shí)接觸的不多,但是了解它卻成為Java開發(fā)的必備基礎(chǔ)——這就是JVM。
在Java中JVM內(nèi)置了垃圾回收的機(jī)制,以守護(hù)進(jìn)程的形式在后臺(tái)自動(dòng)回收垃圾,它讓開發(fā)者無(wú)需關(guān)注空間的創(chuàng)建和釋放,幫助開發(fā)者承擔(dān)對(duì)象的創(chuàng)建和釋放的工作,極大的減輕了開發(fā)的負(fù)擔(dān)。那是不是我們就不需要了解JVM了,顯然在做一些優(yōu)化或者深入研究應(yīng)用性能的時(shí)候,JVM還是起了很關(guān)鍵的作用的。因此本篇就總結(jié)性的描述下垃圾回收相關(guān)的知識(shí)。

哪些內(nèi)存需要回收

回收區(qū)域主要集中在java堆和方法區(qū)。
程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧3個(gè)區(qū)域隨線程而生,隨線程而滅;棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊地執(zhí)行著出棧和入棧操作。每一個(gè)棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來(lái)時(shí)就已知的,因此這幾個(gè)區(qū)域的內(nèi)存分配和回收都具備確定性,所以不需要考慮回收,而Java堆和方法區(qū)則不一樣,一個(gè)接口中的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能不一樣,一個(gè)方法中的多個(gè)分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運(yùn)行期間時(shí)才能知道會(huì)創(chuàng)建哪些對(duì)象,這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的,垃圾收集器所關(guān)注的是這部分內(nèi)存。

什么時(shí)候回收

  • 對(duì)象沒有引用
  • 作用域發(fā)生未捕獲異常
  • 程序在作用域正常執(zhí)行完畢
  • 程序執(zhí)行了System.exit()
  • 程序發(fā)生意外終止(被殺進(jìn)程等)

如何回收

所謂“垃圾”,就是指所有不再存活的對(duì)象。常見的判斷是否存活有兩種方法:引用計(jì)數(shù)法和可達(dá)性分析。

  • 引用計(jì)數(shù)法
    為每一個(gè)創(chuàng)建的對(duì)象分配一個(gè)引用計(jì)數(shù)器,用來(lái)存儲(chǔ)該對(duì)象被引用的個(gè)數(shù)。當(dāng)該個(gè)數(shù)為零,意味著沒有人再使用這個(gè)對(duì)象,可以認(rèn)為“對(duì)象死亡”。但是,這種方案存在嚴(yán)重的問題,就是無(wú)法檢測(cè)“循環(huán)引用”:當(dāng)兩個(gè)對(duì)象互相引用,即時(shí)它倆都不被外界任何東西引用,它倆的計(jì)數(shù)都不為零,因此永遠(yuǎn)不會(huì)被回收。而實(shí)際上對(duì)于開發(fā)者而言,這兩個(gè)對(duì)象已經(jīng)完全沒有用處了。
    因此,Java 里沒有采用這樣的方案來(lái)判定對(duì)象的“存活性”。

  • 可達(dá)性分析
    這種方案是目前主流語(yǔ)言里采用的對(duì)象存活性判斷方案?;舅悸肥前阉幸玫膶?duì)象想象成一棵樹,從樹的根結(jié)點(diǎn) GC Roots 出發(fā),持續(xù)遍歷找出所有連接的樹枝對(duì)象,這些對(duì)象則被稱為“可達(dá)”對(duì)象,或稱“存活”對(duì)象。其余的對(duì)象則被視為“死亡”的“不可達(dá)”對(duì)象,或稱“垃圾”。
    參考下圖,object5,object6 和 object7 便是不可達(dá)對(duì)象,視為“死亡狀態(tài)”,應(yīng)該被垃圾回收器回收。

    可達(dá)性分析

    可作為GC root的對(duì)象
    我們可以猜測(cè),GC Roots 本身一定是可達(dá)的,這樣從它們出發(fā)遍歷到的對(duì)象才能保證一定可達(dá)。那么,Java 里有哪些對(duì)象是一定可達(dá)呢?主要有以下四種:

    • 虛擬機(jī)棧(幀棧中的本地變量表)中引用的對(duì)象。
    • 方法區(qū)中靜態(tài)屬性引用的對(duì)象。
    • 方法區(qū)中常量引用的對(duì)象。
    • 本地方法棧中 JNI 引用的對(duì)象。
      這里只要知道有這么幾種類型的 GC Roots,每次垃圾回收器會(huì)從這些根結(jié)點(diǎn)開始遍歷尋找所有可達(dá)節(jié)點(diǎn)。

垃圾回收算法

上面已經(jīng)知道,所有 GC Roots不可達(dá)的對(duì)象都稱為垃圾,參考下圖,黑色的表示垃圾,灰色表示存活對(duì)象,綠色表示空白空間。


image

那么,我們?nèi)绾蝸?lái)回收這些垃圾呢?

  • Mark-Sweep標(biāo)記-清除算法
    第一步,所謂“標(biāo)記”就是利用可達(dá)性遍歷堆內(nèi)存,把“存活”對(duì)象和“垃圾”對(duì)象進(jìn)行標(biāo)記,得到的結(jié)果如上圖;
    第二步,既然“垃圾”已經(jīng)標(biāo)記好了,那我們?cè)俦闅v一遍,把所有“垃圾”對(duì)象所占的空間直接清空即可。結(jié)果如下:

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

    這便是“標(biāo)記-清理”方案,簡(jiǎn)單方便 ,但是容易產(chǎn)生內(nèi)存碎片。

  • Mark-Compact標(biāo)記-整理算法
    既然上面的方法會(huì)產(chǎn)生內(nèi)存碎片,那好,我在清理的時(shí)候,把所有 存活 對(duì)象扎堆到同一個(gè)地方,讓它們待在一起,這樣就沒有內(nèi)存碎片了。
    結(jié)果如下:

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

    這兩種方案適合存活對(duì)象多,垃圾少的情況,它只需要清理掉少量的垃圾,然后挪動(dòng)下存活對(duì)象就可以了。

  • Copying復(fù)制算法
    這種方法比較粗暴,直接把堆內(nèi)存分成兩部分,一段時(shí)間內(nèi)只允許在其中一塊內(nèi)存上進(jìn)行分配,當(dāng)這塊內(nèi)存被分配完后,則執(zhí)行垃圾回收,把所有存活對(duì)象全部復(fù)制到另一塊內(nèi)存上,當(dāng)前內(nèi)存則直接全部清空。
    參考下圖:

    Copying復(fù)制算法

    起初時(shí)只使用上面部分的內(nèi)存,直到內(nèi)存使用完畢,才進(jìn)行垃圾回收,把所有存活對(duì)象搬到下半部分,并把上半部分進(jìn)行清空。
    這種做法不容易產(chǎn)生碎片,也簡(jiǎn)單粗暴;但是,它意味著你在一段時(shí)間內(nèi)只能使用一部分的內(nèi)存,超過這部分內(nèi)存的話就意味著堆內(nèi)存里頻繁的 復(fù)制清空。
    這種方案適合 存活對(duì)象少,垃圾多 的情況,這樣在復(fù)制時(shí)就不需要復(fù)制多少對(duì)象過去,多數(shù)垃圾直接被清空處理。

  • Generational Collection 分代收集
    最后的這種方法是前面幾種的合體,即目前JVM主要采取的一種方法,思想就是把JVM分成不同的區(qū)域。每種區(qū)域使用不同的垃圾回收方法。

    分代收集

    上面可以看到堆分成三個(gè)區(qū)域:
    新生代(Young Generation):用于存放新創(chuàng)建的對(duì)象,采用復(fù)制回收方法,如果在s0和s1之間復(fù)制一定次數(shù)后,轉(zhuǎn)移到年老代中。這里的垃圾回收叫做minor GC;
    年老代(Old Generation):這些對(duì)象垃圾回收的頻率較低,采用的標(biāo)記整理方法,這里的垃圾回收叫做major GC。
    永久代(Permanent Generation):存放Java本身的一些數(shù)據(jù),當(dāng)類不再使用時(shí),也會(huì)被回收。

    這里可以詳細(xì)的說一下新生代復(fù)制回收的算法流程:
    在新生代中,分為三個(gè)區(qū):Eden, from survivor, to survior。

    • 當(dāng)觸發(fā)minor GC時(shí),會(huì)先把Eden中存活的對(duì)象復(fù)制到to Survivor中;
    • 然后再看from survivor,如果次數(shù)達(dá)到年老代的標(biāo)準(zhǔn),就復(fù)制到年老代中;如果沒有達(dá)到則復(fù)制到to - survivor中,如果to survivor滿了,則復(fù)制到年老代中。
    • 然后調(diào)換from survivor 和 to survivor的名字,保證每次to survivor都是空的等待對(duì)象復(fù)制到那里的。

垃圾回收器

HotSpot 虛擬機(jī)的垃圾收集器
  • 串行收集器 Serial
    這種收集器就是以單線程的方式收集,垃圾回收的時(shí)候其他線程也不能工作。

    串行收集器 Serial

  • 并行收集器 Parallel
    以多線程的方式進(jìn)行收集

    并行收集器 Parallel

  • 并發(fā)標(biāo)記清除收集器 Concurrent Mark Sweep Collector, CMS
    大致的流程為:初始標(biāo)記--并發(fā)標(biāo)記--重新標(biāo)記--并發(fā)清除

    并發(fā)標(biāo)記清除收集器 Concurrent Mark Sweep Collector, CMS

  • G1收集器 Garbage First Collector
    大致的流程為:初始標(biāo)記--并發(fā)標(biāo)記--最終標(biāo)記--篩選回收

    G1收集器 Garbage First Collector

GC什么時(shí)候觸發(fā)

由于對(duì)象進(jìn)行了分代處理,因此垃圾回收區(qū)域、時(shí)間也不一樣。GC有兩種類型:Scavenge GC和Full GC。

  • Scavenge GC
    一般情況下,當(dāng)新對(duì)象生成,并且在Eden申請(qǐng)空間失敗時(shí),就會(huì)觸發(fā)Scavenge GC,對(duì)Eden區(qū)域進(jìn)行GC,清除非存活對(duì)象,并且把尚且存活的對(duì)象移動(dòng)到Survivor區(qū)。然后整理Survivor的兩個(gè)區(qū)。這種方式的GC是對(duì)年輕代的Eden區(qū)進(jìn)行,不會(huì)影響到年老代。因?yàn)榇蟛糠謱?duì)象都是從Eden區(qū)開始的,同時(shí)Eden區(qū)不會(huì)分配的很大,所以Eden區(qū)的GC會(huì)頻繁進(jìn)行。因而,一般在這里需要使用速度快、效率高的算法,使Eden去能盡快空閑出來(lái)。

  • Full GC
    對(duì)整個(gè)堆進(jìn)行整理,包括Young、Tenured和Perm。Full GC因?yàn)樾枰獙?duì)整個(gè)堆進(jìn)行回收,所以比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC的次數(shù)。在對(duì)JVM調(diào)優(yōu)的過程中,很大一部分工作就是對(duì)于Full GC的調(diào)節(jié)。有如下原因可能導(dǎo)致Full GC:

    • 年老代(Tenured)被寫滿
    • 持久代(Perm)被寫滿
    • System.gc()被顯示調(diào)用
    • 上一次GC之后Heap的各域分配策略動(dòng)態(tài)變化

參考鏈接:
http://www.importnew.com/26821.html
https://www.cnblogs.com/xing901022/p/7725961.html
https://www.cnblogs.com/1024Community/p/honery.html
https://blog.csdn.net/sinat_33087001/article/details/77030118

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 哪些內(nèi)存需要回收 由于程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧的生命周期都跟隨線程的生命周期,當(dāng)線程銷毀了,內(nèi)存也就回收了...
    Samuel_Tom閱讀 7,532評(píng)論 1 9
  • 一、概述: 本文作為大數(shù)據(jù)基礎(chǔ)的Java部分,上一篇說明了Linux的常用命令,今天學(xué)習(xí)了Java的高級(jí)特性,我們...
    慕久久閱讀 786評(píng)論 1 5
  • 1. 概述 在Java內(nèi)存區(qū)域里講了Java的內(nèi)存運(yùn)行時(shí)數(shù)據(jù)區(qū)域分為如下5個(gè)部分 程序計(jì)數(shù)器(Program Co...
    謝樸歡閱讀 321評(píng)論 0 0
  • 1. 前言 網(wǎng)上關(guān)于jvm gc的文章有很多,寫這篇文章不是有什么新東西要講,主要原因是工作時(shí)也偶爾碰到比如ful...
    aaron1993閱讀 1,389評(píng)論 0 0
  • 本文主要淺談JAVA回收機(jī)制,讓初學(xué)者對(duì)這一塊大概有個(gè)簡(jiǎn)單的認(rèn)識(shí),同時(shí)也記錄下自己學(xué)習(xí)的成果,溫故而知新。 疑問 ...
    南山羊閱讀 750評(píng)論 0 1

友情鏈接更多精彩內(nèi)容