Java虛擬機(jī)垃圾回收

一、內(nèi)存回收的關(guān)注區(qū)域

JMM章節(jié)中介紹了Java虛擬機(jī)內(nèi)存模型的幾個(gè)區(qū)域,對(duì)于程序計(jì)數(shù)器、虛擬機(jī)棧和本地方法棧都是線程私有的,伴隨著線程由生到滅,這幾個(gè)區(qū)域的內(nèi)存分配這回收都有一定確定性(因?yàn)樗純?nèi)存大小基本是編譯可知的),因此,內(nèi)存回收主要的關(guān)注對(duì)象是堆和方法區(qū)。我們只有在程序運(yùn)行時(shí)才能確定會(huì)創(chuàng)建哪些對(duì)象,這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的。垃圾收集器主要關(guān)注的是這部分內(nèi)存。

二、對(duì)象可被回收的判斷標(biāo)準(zhǔn)

  1. 引用計(jì)數(shù)法

    思想:就是對(duì)每個(gè)對(duì)象添加一個(gè)引用計(jì)數(shù)器,使用該算法的微軟的com技術(shù)等

    弊端:這種方式存在一個(gè)問題就是循環(huán)引用,導(dǎo)致內(nèi)存泄露
  2. 可達(dá)性分析法

    思想:就是通過一系列稱為GC Roots的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈。

    Java語言中,可以作為GC Roots對(duì)象包括下面幾種:
  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
  • 本地方法棧中(Native方法)引用的對(duì)象
  • 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
  • 方法區(qū)中常量引用的對(duì)象
  1. 引用相關(guān)知識(shí)

    一般意義上講,引用就代表了一個(gè)對(duì)象的內(nèi)存地址或者存有沒有對(duì)象地址的句柄,但是這種定義導(dǎo)致一個(gè)對(duì)象只存在引用和未被引用兩種狀態(tài)。而在實(shí)際應(yīng)用中,一個(gè)對(duì)象需要更多的狀態(tài),來提高效率和優(yōu)化性能。因此,Java引用分為4種:
  • 強(qiáng)引用:代碼中最常見的 Object obj = new Object();只要引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收被引用的對(duì)象。
  • 軟引用:用來描述一些還有用但是非必須的對(duì)象。對(duì)于軟引用關(guān)聯(lián)的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)將這些對(duì)象列進(jìn)可回收范圍之中進(jìn)行第二次回收,如果這次回收還是沒有足夠的內(nèi)存,那么才會(huì)拋出內(nèi)存溢出異常。通過SoftReference來實(shí)現(xiàn)。
  • 弱引用:也是用來描述非必須的對(duì)象。弱引用只能生存到下一次垃圾收集之前。當(dāng)垃圾收集器開始工作時(shí),無論內(nèi)存是否足夠,都會(huì)回收這部分內(nèi)存。通過WeakReference來實(shí)現(xiàn)。
  • 虛引用:一個(gè)對(duì)象是否有虛引用,完全不會(huì)對(duì)其生存周期構(gòu)成影響,也無法通過虛引用來取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用的唯一目的就是能在這個(gè)對(duì)象被垃圾收集器回收時(shí)能夠收到一個(gè)系統(tǒng)通知。通過PhantomReference來實(shí)現(xiàn)。
  1. 對(duì)象是否直接死亡

    對(duì)于可達(dá)性分析算法中不可達(dá)的對(duì)象,也不是“非死不可”,要真正宣告對(duì)象死亡,必須經(jīng)歷兩次標(biāo)記過程。如果可達(dá)性分析不可達(dá)時(shí),對(duì)象會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選,篩選標(biāo)準(zhǔn)是對(duì)象是否有必要執(zhí)行finalize()方法。當(dāng)對(duì)象沒有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)都將這兩種情況視為沒有必要執(zhí)行。如果這個(gè)對(duì)象被判定位必要執(zhí)行finalize()方法,則會(huì)被放在一個(gè)F-Queue隊(duì)列中,并且會(huì)在稍后由一個(gè)虛擬機(jī)自建的Finalize線程去執(zhí)行它。一個(gè)對(duì)象的finalize()方法只會(huì)被執(zhí)行一次,一個(gè)對(duì)象可以在finalize()完成一次自救,并且只能自救一次。

三、垃圾回收

  1. 回收方法區(qū)

    方法區(qū)的回收對(duì)象主要是廢棄常量和無用的類。對(duì)于一個(gè)字符串常量“hello”,如果當(dāng)前系統(tǒng)中沒有任何一個(gè)String對(duì)象叫做“hello”,那么就認(rèn)為沒有任何對(duì)象引用常量池中的“hello”常量,這時(shí)該常量就是廢棄常量。無用的類的判斷標(biāo)準(zhǔn)則比較復(fù)雜,需要滿足一下條件:
  • 該類的所有實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類的任何實(shí)例。
  • 加載該類的ClassLoader已經(jīng)被回收。
  • 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有任何地方引用,無法在任何地方通過反射來訪問該類的方法。

    滿足上面三個(gè)條件的無用類可以被回收,但是并不是必然回收。還需要看虛擬機(jī)參數(shù)設(shè)置
  1. 垃圾收集算法
  • 標(biāo)記-清除算法

    思想:首先標(biāo)記出所偶遇需要回收的對(duì)象,標(biāo)記完成之后進(jìn)行統(tǒng)一回收。

    弊端:標(biāo)記和清除的效率很低,另外就是會(huì)產(chǎn)生大量不連續(xù)的碎片,碎片過多可能會(huì)導(dǎo)致后續(xù)對(duì)象分配內(nèi)存時(shí),無法找到適合內(nèi)存空間而進(jìn)一步促發(fā)下一次垃圾收集動(dòng)作
  • 復(fù)制算法

    將可用內(nèi)存一份為二,每次使用其中一塊,當(dāng)一塊內(nèi)存使用完之后,將還存活的對(duì)象復(fù)制到另外一塊上,然后將使用的這一塊給一次清理掉。
    弊端:將可用內(nèi)存空間一半空閑,代價(jià)過高

    實(shí)際使用中大多數(shù)對(duì)象都朝生夕滅,所以優(yōu)化方法就是講內(nèi)存分配默認(rèn)Eden、和兩塊相等的Survivor空間8:1:1,每次垃圾回收時(shí)將還存活的對(duì)象復(fù)制到空閑的那一塊survivor中,這樣每次就只會(huì)浪費(fèi)10%的內(nèi)存空間。當(dāng)然,當(dāng)survivor不夠保存剩余存活的對(duì)象時(shí),需要到老年代擔(dān)保,也就是當(dāng)超出時(shí),將對(duì)象放到老年代。
  • 標(biāo)記-整理算法

    標(biāo)記過程和標(biāo)記-清除算法一樣,只是在清除時(shí),會(huì)將所有存活的對(duì)象移動(dòng)到一端,然后直接清理端外界的的內(nèi)存。
  1. 垃圾收集器
  • Serial收集器

    這個(gè)收集器是一個(gè)單線程的收集器,這里單線程的意思是當(dāng)GC線程開始工作時(shí),其他的用戶線程都必須暫停,當(dāng)?shù)竭_(dá)安全點(diǎn)之后,GC線程新生代中采用復(fù)制算法,老年代中采用標(biāo)記-整理算法進(jìn)行垃圾回收

    弊端:需要暫停,非常影響體驗(yàn)

    優(yōu)勢(shì):簡(jiǎn)單高效,不會(huì)有線程交互的開銷,如果停頓時(shí)間控制得當(dāng),不要頻繁發(fā)生,則還可以接受,一般用于運(yùn)行在Client模式的虛擬機(jī)
  • ParNew收集器

    多線程版本的Serial收集器。使用多線程來進(jìn)行垃圾回收。許多運(yùn)行在server模式的虛擬機(jī)首選新生代收集器,原因是,除了Serial以外,只有它可以與CMS收集器配合使用。
  • Parallel Scavenge收集器

    CMS等收集器的關(guān)注點(diǎn)是如何減少垃圾回收時(shí),用戶線程的等待時(shí)間,而Parallel Scavenge收集器則是達(dá)到一個(gè)可控制的吞吐量 吞吐量 = 執(zhí)行用戶代碼時(shí)間/(執(zhí)行用戶代碼時(shí)間+垃圾收集時(shí)間)。停頓時(shí)間短適合交互性比較強(qiáng),需要良好的響應(yīng)速度來提升用戶體驗(yàn),而高吞吐量則可以高效的利用cpu時(shí)間,盡快完成程序的運(yùn)算任務(wù)。
  • Serial Old收集器

    Serial收集器的老年代版本,使用標(biāo)記-整理方法,一個(gè)是與Parallel Scavenge配合使用,第二點(diǎn)就是作為CMS的后備方案
  • Parallel Old收集器

    Parallel Scavenge的老年代版本,它出現(xiàn)之前,新生代的收集器Parallel只能和Serial Old配合使用
  • CMS收集器

    這種收集器主要獲取最短回收停頓時(shí)間為目標(biāo),主要用于互聯(lián)網(wǎng)站等系統(tǒng)的服務(wù)端。標(biāo)記-清除算法實(shí)現(xiàn)。其垃圾收集的過程分為四步:

    初始標(biāo)記:主要標(biāo)記GC root能夠直接關(guān)聯(lián)的對(duì)象,時(shí)間短

    并發(fā)標(biāo)記:GC root tracing的過程,時(shí)間長(zhǎng),但是與用戶線程并發(fā)執(zhí)行

    重新標(biāo)記:修正并發(fā)標(biāo)記過程中因用戶程序繼續(xù)運(yùn)用而產(chǎn)生變動(dòng)的那一部分對(duì)象,時(shí)間短

    并發(fā)清除:時(shí)間長(zhǎng),與用戶線程并發(fā)執(zhí)行

    缺點(diǎn):對(duì)cpu資源非常敏感,因?yàn)椴l(fā)執(zhí)行階段會(huì)占用線程執(zhí)行的cpu資源,當(dāng)cpu較少時(shí)會(huì)導(dǎo)致系統(tǒng)吞吐量很低。無法處理浮動(dòng)的垃圾,可能會(huì)出現(xiàn)(concurrent mode failure)失敗而導(dǎo)致另一次full gc的出現(xiàn),在并發(fā)清除階段用戶線程還會(huì)生產(chǎn)垃圾。還有一個(gè)缺點(diǎn)就是標(biāo)記-清除,會(huì)產(chǎn)生大量空間碎片。
  • G1收集器
    是一款面向服務(wù)端的垃圾收集器,特點(diǎn):并發(fā)和并行,分代收集,空間整合,整體上看像標(biāo)記-整理,局部像復(fù)制,可預(yù)測(cè)的停頓。它將整個(gè)堆分為多個(gè)大小相等的區(qū)域,雖然保留了新生代和老年代的概念,但是不再是隔離的了。跟蹤每個(gè)區(qū)域垃圾堆積的大小,每次回收時(shí),優(yōu)先回收價(jià)值最大的region。

    垃圾收集的步驟:

    初始標(biāo)記:

    并發(fā)標(biāo)記:

    最終標(biāo)記:

    篩選回收:

    四個(gè)過程和cms垃圾收集器很類似,最后一步不同在于,g1是挑選最具有回收的價(jià)值的區(qū)域開始回收

4.GC 日志

關(guān)鍵點(diǎn):GC、Full GC、回收前的容量、回收后的容量、總的容量、回收的區(qū)域、回收時(shí)間a

?著作權(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)容