回收分為兩大塊,一為搜尋,二為回收。
一,搜尋
GC(Garbage Collector)在回收物件前首先必須發現那些無用的物件,如何去發現定位這些無用的物件?
1,引用計數演算法(基本棄用)
引用計數器演算法是給每個物件設定一個計數器,當有地方引用這個物件的時候,計數器+1,當引用失效的時候,
計數器-1,當計數器為 0 的時候,JVM 就認為物件不再被使用,是“垃圾”了。
引用計數器實現簡單,效率高;但是不能解決迴圈引用問問題(A 物件引用 B 物件,B 物件又引用 A 物件,但是
A,B 物件已不被任何其他物件引用),同時每次計數器的增加和減少都帶來了很多額外的開銷,所以在 JDK1.1 之後,
這個演算法已經不再使用了。
2,根達搜尋演算法(目前使用中)
根搜尋演算法是通過一些“GC Roots”物件作為起點,從這些節點開始往下搜尋,搜尋通過的路徑成為引用鏈
(Reference Chain),當一個物件沒有被 GC Roots 的引用鏈連線的時候,說明這個物件是不可用的。
複製程式碼
gcroot物件:
a) 虛擬機器棧(棧幀中的本地變數表)中的引用的物件。
b) 方法區域中的類靜態屬性引用的物件。
c) 方法區域中常量引用的物件。
d) 本地方法棧中 JNI(Native 方法)的引用的物件。
複製程式碼
二,回收
1,標記清除演算法
標記—清除演算法包括兩個階段:“標記”和“清除”。在標記階段,確定所有要回收的物件,並做標記。清除階
段緊隨標記階段,將標記階段確定不可用的物件清除。標記—清除演算法是基礎的收集演算法,標記和清除階段的效率不
高,而且清除後回產生大量的不連續空間,這樣當程式需要分配大記憶體物件時,可能無法找到足夠的連續空間。
2,複製演算法
複製演算法是把記憶體分成大小相等的兩塊,每次使用其中一塊,當垃圾回收的時候,把存活的物件複製到另一塊上,
然後把這塊記憶體整個清理掉。複製演算法實現簡單,執行效率高,但是由於每次只能使用其中的一半,造成記憶體的利用
率不高。現在的 JVM 用複製方法收集新生代,由於新生代中大部分物件(98%)都是朝生夕死的,所以兩塊記憶體的比
例不是 1:1(大概是 8:1)。
3,標記整理演算法
標記—整理演算法和標記—清除演算法一樣,但是標記—整理演算法不是把存活物件複製到另一塊記憶體,而是把存活對
象往記憶體的一端移動,然後直接回收邊界以外的記憶體。標記—整理演算法提高了記憶體的利用率,並且它適合在收集物件
存活時間較長的老年代。
4,分代收集(當今最常用的方法)
將物件按生命週期不同劃分:
年輕代(Young Generation)(Eden,Survivor-s0,Survivor-s1)
年老代(Old Generation)
持久代(Permanent Generation)。(包含應用的類/方法資訊, 以及JRE庫的類和方法資訊.和垃圾回收基本無關)
1)建立新物件,一般將直接放入新生代Eden區域,大物件將直接放入年老代。
2)當Eden區域記憶體分配完畢,小Gc觸發,根達性分析的可達物件將進入Survivor區域-s0,並清空Eden區域。不可達物件將直接刪除。
3)當Eden區域再次記憶體分配完畢時候,小gc觸發,根達性分析的可達物件將進入Survivor-s1區域,同時,Survivor-s0區域觸發小gc,其中可達物件移動到Survivor-s1區域,企鵝年齡+1,並清空Survivor-s0,。
4)Eden又填滿之後,Survivor-s0與Survivor-s1,互換標籤,Eden區域可達物件進入Survivor-s0,Survivor-s1觸發小gc,可達物件進入Survivor-s0,並且年齡+1.
5)重複上述過程,達到一定時候,進入年老代。複製程式碼