1. 程式人生 > 程式設計 >垃圾物件判斷與回收

垃圾物件判斷與回收

瞭解GC如何進行垃圾回收,也就是垃圾回收器如何工作,我們首先要了解這幾個前置知識.如何判斷一個物件是否是垃圾物件,方法區中載入的類元資訊如何判斷是無用類,垃圾回收演演算法,物件的最終標記.

如何判斷一個物件是否是垃圾物件

引用記數法

顧名思義,這個方法其實就是判斷物件的引用數量來判斷這個物件是否是垃圾物件.給物件新增引用計數器,每多一個引用則引用數量+1,每少一個引用則引用數量-1.當此物件引用數量為零,則物件為垃圾物件.這種方式實現簡單且高效,但是這種方式存在著一種問題,也就是物件的迴圈依賴.假設現在有兩個物件,他們互相引用值為null,且不存在其他物件對其引用也不存在這兩個物件的使用,那麼這時這兩個物件應該就是垃圾物件,但是由於他們互相引用,所以無法被回收.

可達性分析演演算法

我們還是根據名字推測他的意思,可達性即這個物件可以到達,對可達性進行分析來判斷物件是否存在引用,如果物件可達則不能回收,如果不可達則將物件標記為垃圾物件等待回收,這也是主流虛擬機器器採用的方式,那麼這個可達性是什麼呢,要了解這個概念,我們首先需要了解GC Roots根節點.

GC Roots根

我們都知道java是單根結構,可以根據一個Object根發展出一顆依賴樹來.那麼這個GC Roots根節點其實也有著異曲同工之妙.他同樣的可以以GC Root作為根節點向下發展,發展出一顆依賴樹,通過這個GC Root向下探尋,可以探尋到的物件我們就稱其為可達的.值得注意的一點是GC Root可以有多個.

GC Roots根是什麼

GC Roots有很多種型別,常見的比如說棧幀裡的本地變數(指向堆記憶體的物件),靜態變數等等

無用類

無用類(並非學名)需要符合以下幾個條件

  • 這個類的所有引用已經消失(被回收)
  • 這個類的class資訊被回收(不能通過反射建立此物件的例項)
  • 這個類的類載入器被回收

垃圾物件標記

垃圾物件的標記其實是分為兩個階段的,主要是看這個物件是否重寫了Object類的finalize()方法.

通過可達性分析我們可以對垃圾物件進行標記.但是物件其實是有一個二次標記的過程的,重寫finalize()方法可以進行第二次標記,如果物件沒有重寫這個方法則直接視為垃圾物件.這一些執行第二次標記的物件,會執行這個方法內的程式碼,如果在這個方法中把本來應該是垃圾物件的物件重新引用,比如設定一個成員變數並將這個例項賦給這個變數.那麼這個物件就不會被視為垃圾物件,如果沒有對其進行重新引用則此物件視為垃圾物件.

垃圾回收演演算法

垃圾回收演演算法主要分為四種

標記-清除演演算法

這個演演算法分為兩個部分,"標記","清除".

標記即上述內容,通過可達性分析演演算法,不可達物件標記,二次標記中沒有自救的物件.

清除就是進行消除工作

這個演演算法具有兩個問題,效率問題與空間問題.

  • 效率問題:由於垃圾物件存在不連續又沒有引用,所以標記的過程很耗時
  • 空間問題:這種清除演演算法會造成一些微小的記憶體碎片,從而造成記憶體的損失.

記憶體碎片就是佔用空間小沒法放下其他物件的的小記憶體空間.

標記-複製演演算法

這個演演算法將一塊記憶體區域分為完全相等的兩塊,這兩塊只能使用一塊,另一塊做什麼用呢,用於垃圾清除後的空間整理.進行垃圾清除時,他會把當前這一半的所有存活物件依次移入到另一半記憶體中去,移入完成後在對這一塊記憶體進行整體回收.由於每次清理物件實際都存在一個整理的過程,所以這種演演算法不會產生垃圾碎片問題,又因為他是保留有用的物件,直接全部回收一塊記憶體空間,速度自然也比標記-清除演演算法快很多.

標記-整理演演算法

標記過程都相同,整理首先是將存活物件向前移動,將所有存活物件移到前面去,那麼此時會產生一個記憶體分界點,分界點一端全部都是有用物件,另一端全部都是垃圾物件,這時候只需要對分界點的一端進行整體清理就可以了.這個演演算法同樣不會產生垃圾碎片,效率也不低.

分代收集演演算法

分代收集演演算法並不是像前三種演演算法那樣做一個清除的步驟,他只不過是將堆記憶體分為了年輕代老年代,這樣我們就可以通過不同的策略針對不同的區採取不同的垃圾收集演演算法.