前言
在堆中幾乎存放著所有的對(duì)象實(shí)例,垃圾收集器在對(duì)堆進(jìn)行回收前,要判斷哪些對(duì)象是“活著”的,哪些不可能再被任何途徑所使用。首先介紹兩種判讀對(duì)象是否活著的算法。
引用計(jì)數(shù)法
- 概念:引用計(jì)數(shù)法是給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,當(dāng)有一個(gè)地方引用時(shí),計(jì)數(shù)器加1,當(dāng)引用失敗,計(jì)數(shù)器值減1,任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。(Java虛擬機(jī)沒(méi)有采用該方法)
- 優(yōu)勢(shì):實(shí)現(xiàn)簡(jiǎn)單,判定效率高。
- 缺點(diǎn):無(wú)法解決對(duì)象之間相互循環(huán)引用的問(wèn)題。
可達(dá)性分析算法
-
概念:通過(guò)一系列的稱(chēng)為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱(chēng)為引用連,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連(GC Roots到這個(gè)對(duì)象不可達(dá))時(shí),證明這個(gè)對(duì)象是不可用的。
可達(dá)性
由上圖可知通過(guò)GC Roots對(duì)象1,2,3,4均是可達(dá)的,而對(duì)象5,6,7通過(guò)GC Roots是不可達(dá)的,所以它們會(huì)被判定為可回收對(duì)象。
- GC Roots對(duì)象包含以下幾種:
- 虛擬機(jī)棧中引用的對(duì)象
- 方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 本地方法棧中JNI(一般說(shuō)的Native方法)引用的對(duì)象
生存還是死亡
一個(gè)對(duì)象真正的死亡需要經(jīng)歷兩次標(biāo)記過(guò)程,若對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒(méi)有與GC Roots相連接的引用鏈,那它會(huì)被第一標(biāo)記且進(jìn)行一次篩選,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法,當(dāng)對(duì)象沒(méi)有覆蓋finalize()方法,或該方法已經(jīng)被虛擬機(jī)調(diào)用過(guò),虛擬機(jī)將這兩種情況視為“沒(méi)有必要執(zhí)行”。這類(lèi)對(duì)象會(huì)被放置在yigeF-Queue隊(duì)列中,由一個(gè)低優(yōu)先級(jí)的線(xiàn)程去執(zhí)行。稍后GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記,若對(duì)象在finalize()方法中重新與引用鏈上的任意對(duì)象建立了關(guān)聯(lián),那在第二次標(biāo)記時(shí)它將被移除出“即將回收的集合”,否則會(huì)被回收。
垃圾回收算法
- 標(biāo)記-清除算法。首先標(biāo)記出需要回收的對(duì)象,然后再統(tǒng)一回收。缺點(diǎn):1、效率問(wèn)題,標(biāo)記和清除效率均不高;2、標(biāo)記和清除兩個(gè)過(guò)程會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片。
- 復(fù)制算法。首先將可用內(nèi)存按照容量分為大小相等的兩塊,每次只使用一塊,用完了就將還活著的對(duì)象復(fù)制到另一塊上,然后把使用過(guò)的內(nèi)存空間進(jìn)行一次清理。優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行效率高。缺點(diǎn):將內(nèi)存縮小為原來(lái)的一半。為了解決這個(gè)問(wèn)題,提出將內(nèi)存劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,默認(rèn)比例是8:1:1。工作機(jī)制是首先在Eden區(qū)創(chuàng)建對(duì)象,Eden區(qū)滿(mǎn)了,經(jīng)過(guò)一次GC后還活著的對(duì)象復(fù)制到Survivor0,清空Eden區(qū);又開(kāi)始在Eden區(qū)創(chuàng)建對(duì)象,再次滿(mǎn)了,把Eden區(qū)和Survivor0中活著的對(duì)象copy到Survivor1,清空Eden和Survivor0;當(dāng)對(duì)象太大或達(dá)到了在Survivor區(qū)最大移動(dòng)次數(shù)都會(huì)被放到old空間。
- 標(biāo)記整理算法。標(biāo)記過(guò)程與標(biāo)記清除一樣。后續(xù)步驟不是直接回收對(duì)象,而是讓所有存活的對(duì)象移到一端,然后直接清理掉端邊界以外的內(nèi)存。
- 分代收集算法。該算法是根據(jù)對(duì)象存活周期的不同劃分為幾塊。一般是把Java堆分為新生代和老年代,根據(jù)各個(gè)年齡的特點(diǎn)采用最合適的收集算法。新生代采用復(fù)制算法,老年代采用標(biāo)記-清除或者標(biāo)記整理進(jìn)行回收。
HopSpot算法實(shí)現(xiàn)
采用的是準(zhǔn)確式GC,虛擬機(jī)可以知道哪些地方存著對(duì)象引用。采用一組稱(chēng)為OopMap的數(shù)據(jù)結(jié)構(gòu)來(lái)達(dá)到這個(gè)目的。在類(lèi)加載完成的時(shí)候,HotSpot就把對(duì)象內(nèi)什么偏移量上什么類(lèi)型的數(shù)據(jù)計(jì)算出來(lái),在JIT編譯過(guò)程中,也會(huì)在特定的位置記錄下棧和寄存器中哪些位置是引用。
安全點(diǎn):前面提到的在“特定的位置”記錄這些信息,這些位置稱(chēng)為安全點(diǎn)(Safepoint),即程序執(zhí)行時(shí)并非在所有地方都能停頓下來(lái)GC,只有到達(dá)安全點(diǎn)時(shí)才能暫停。
安全點(diǎn)的選定是以程序“是否有讓程序長(zhǎng)時(shí)間執(zhí)行的特征”為標(biāo)準(zhǔn)進(jìn)行選定的。對(duì)于Safepoint而言另一個(gè)問(wèn)題是如何在GC發(fā)生時(shí),所有線(xiàn)程都能跑到最近的安全點(diǎn)再停下來(lái)。有兩種方案可以選擇,分別是搶先式中斷和主動(dòng)式中斷
- 搶先式中斷,在GC發(fā)生時(shí)首先把所有線(xiàn)程全部中斷,如果發(fā)現(xiàn)有的線(xiàn)程沒(méi)有在安全點(diǎn)上就恢復(fù)線(xiàn)程,讓它跑到安全點(diǎn)上。該方法已經(jīng)被棄用。
- 主動(dòng)式中斷,當(dāng)GC需要中斷線(xiàn)程的時(shí)候,不直接堆線(xiàn)程進(jìn)行操作,僅僅簡(jiǎn)單設(shè)置一個(gè)標(biāo)識(shí),各個(gè)線(xiàn)程執(zhí)行時(shí)主動(dòng)區(qū)輪詢(xún)這個(gè)標(biāo)識(shí),輪詢(xún)標(biāo)識(shí)的地方與安全點(diǎn)是重合的。
安全區(qū):是指在一段代碼片段之中,引用關(guān)系不會(huì)發(fā)生變化,這個(gè)區(qū)域中的任意地方開(kāi)始GC都是安全的。可以把Safe Region看作是被擴(kuò)展了的安全點(diǎn)。主要是解決了當(dāng)線(xiàn)程處于Sleep或者Blocked狀態(tài)時(shí),線(xiàn)程無(wú)法響應(yīng)JVM中斷請(qǐng)求,“走”到安全的地方去掛起的問(wèn)題。
