博客起名為Java垃圾收集機(jī)制,給人的感覺就像是垃圾收集是Java語言特有的。事實(shí)上,垃圾收集(Garbage Collection)遠(yuǎn)比Java久遠(yuǎn)。垃圾收集需要考慮3件事情:哪些內(nèi)存需要回收、什么時候回收、如何回收。帶著這三個問題,我們?nèi)タ纯碕ava是如何實(shí)現(xiàn)垃圾回收的。
Java的垃圾回收(GC)機(jī)制主要作用于運(yùn)行時數(shù)據(jù)區(qū)的哪些部分呢?在上篇博客“Java虛擬機(jī)工作原理”中我們介紹了JVM運(yùn)行時數(shù)據(jù)區(qū)有程序計數(shù)器、虛擬機(jī)棧、本地方法棧、堆、方法區(qū)5個區(qū)域。其中前三個區(qū)域隨線程的創(chuàng)建而創(chuàng)建,隨線程的消亡而消亡;棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊地執(zhí)行出棧和入棧操作。因此這三個區(qū)域的不需要過多的考慮垃圾回收問題。而Java堆和方法區(qū)則不一樣一個接口的多個實(shí)現(xiàn)類需要的內(nèi)存可能不一樣,一個方法中的多個分支需要的內(nèi)存也可能不一樣,只有在程序運(yùn)行期間才能知道會創(chuàng)建哪些對象,這部分內(nèi)存的分配和回收都是動態(tài)的。因此垃圾收集器所關(guān)注的也就是這部分內(nèi)存。
回到垃圾收集的第一件事上:哪些內(nèi)存需要回收?Java堆中存放著程序中幾乎所有的對象實(shí)例,垃圾收集器在對堆進(jìn)行回收前,首先需要判斷哪些對象還“活著”,哪些已經(jīng)“死去”。通常判斷的方法有引用計數(shù)算法、可達(dá)性分析算法。引用計數(shù)算法給對象中添加一個引用計數(shù)器,每當(dāng)一個地方引用它時,計數(shù)器值加1;當(dāng)引用失效時,計數(shù)器值減1,如果計數(shù)器的值為0,則說明對象不再被使用(死去了)。然而Java虛擬機(jī)中并沒有選用計數(shù)算法來管理內(nèi)存,因為引用計數(shù)算法難以解決對象之間相互循環(huán)引用的問題??蛇_(dá)性分析算法是將一系列稱為“GC Roots”的對象作為起始節(jié)點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的(也死去了)。其中可作為GC Roots對象的有:虛擬機(jī)棧(棧幀中本地變量表)中引用的對象,方法去中靜態(tài)屬性引用的對象,方法區(qū)中常量引用的對象,本地方法棧中引用的對象。上邊說的都是Java堆中的內(nèi)存回收,而方法區(qū)(HotSpot中的永久代)的垃圾收集主要回收兩部分內(nèi)容:廢棄常量和無用類。判斷一個常量是否是廢棄常量只需判斷是否還存在對該常量有引用的對象。而判斷無用類需要同時滿足3個條件:該類所有的實(shí)例都已經(jīng)被回收,即Java堆中不存在該類的任何實(shí)例;加載該類的ClassLoader已經(jīng)被回收;該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
垃圾收集算法
標(biāo)記-清除算法(Mark-Sweep算法)
算法分為兩個部分(標(biāo)記、清除),首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。該算法主要有兩個不足:一個是效率問題,標(biāo)記和清除兩個過程的效率都不高;一個是空間問題,標(biāo)記清除后會產(chǎn)生大量的內(nèi)存碎片。標(biāo)記清除算法的執(zhí)行過程如下圖所示: