1、判斷對象是否存活的算法:
<1>、引用計數法:(每個對象實例中都有一個引用計數器)當一個對象被創建的時候,該實例對象分配給一個變量,此時計數器爲1。之後當其他變量引用該對象實例時,計數器+1,而當引用的變量超過生命週期,或改變爲引用其他對象實例時,該對象實例計數器-1。當計數器爲0的時候,則說明該對象實例沒有被引用,即可以被垃圾回收掉。
缺點:當2個對象之間相互引用時,則這2個對象實例無法被回收掉,因爲他們之間相互引用。
關於缺點的理解,僞代碼說明:
{
O1 o1 = new O1();
O2 o2 = new O2();
o1.instance = o2;//實例O1中的一個成員變量是實例O2類型
o2.instance = o1;//實例O2中的一個成員變量是實例O1類型
o1 = null;//雖然o1 = null ,但是O2中的一個成員變量引用了O1的實例,即引用變量由 o1—>O2.instance
o2 = null;同理如上
}
<2>、可達性分析:由定義好的GC Roots節點作爲起始點開始,將所有有聯繫的節點全部連接到一起,最終形成類似樹狀的結構,此時,不在樹上的節點即爲可回收對象
a、優點:解決了互相引用,無法回收的問題
b、GC Roots節點對象有如下幾種:
1、方法區常量引用的對象
2、方法區中靜態成員變量引用的對象
3、虛擬機棧中引用的對象【棧楨中的本地變量表】
4、本地方法棧中本地方法引用的對象
c、二次標記機制:在可達性分析中,當被確定爲可回收對象時,進行第一次標記。若該對象沒有重寫 finalize(),或者在重寫時,沒有爲自己與GC Roots 樹建立連接,此時進行第二次標記。二次標記結束後,當垃圾回收的時候,就會被回收掉。
注意:二次標記機制,finalize()中建立與GC Roots 樹連接,一個對象只有一次機會,不會通過在finalize()中建立與GC Roots 樹連接,出現一直無法回收的現象。
2、方法區垃圾回收
方法區中被回收的對象有2種:1、廢棄常量;2、無用的類
<1>、廢棄常量通過可達性分析 來判斷
<2>、無用的類通過以下幾點來判斷是否回收:
1、該類所有的實例都被回收
2、加載該類的ClassLoader 被回收
3、該類對應的Java.lang.Class 對象沒有任何地方引用,同時無法通過反射來獲取該類的方法等等。
3、垃圾回收的算法
<1>、標記清除算法
採用從 GC Roots 點開始掃描,對存活的對象進行標記,標記結束之後,再掃描整個空間中未被標記的對象進行回收。
缺點:產生內存碎片
<2>、複製回收算法 --- 解決句柄的開銷和內存碎片的問題
將空間分爲2部分,同樣從GC Roots節點開始掃描,將存活的對象複製到另外一半中,全部掃描結束後,清空原來一半空間。
優點:不會產生內存碎片,高效
缺點:佔用內存空間大
<3>、標記整理算法
標記所有存活的對象,掃描並清除整個空間中未標記的對象,在回收死亡的對象時,將存活的對象往左邊空閒空間移動,建立在標記清除算法之上
優點:不會產生內存碎片
缺點:開銷大
<4>、分代收集算法
目前大部分JVM使用的垃圾回收算法,核心思想是根據對象生命週期劃分爲若干個不同區域:新生代、老年代。新生代由於每次回收時都有大量的對象需要被回收、老年代的特點是每次垃圾回收時,只有少量對象被回收。因此,可以根據不同的區域使用不同的垃圾回收算法。
年輕代回收算法-複製回收算法(分區比例是8:1:1);老年代中回收的對象少,適合標記整理 和標記清除算法
過程:
將年輕代分爲 Eden區、s0(From)區、s1(To)區。新對象產生一般在Eden區,如果Eden區滿了,則將Eden區中存活的對象複製到s0區-箭頭1,清空Eden區【young GC】;如果s0區滿了,則將Eden區和s0區中存活的對象複製到s1區-箭頭2,清空Eden區 和 s0區【young GC】,同時將s0於s1 互換一下 -箭頭2.5,確保s1區是空的。同時如果其中的對象達到了閾值(在s區中經歷默認爲15次young GC之後),就將達到閾值的對象放入老年代中。
如果s1區不足以存放Eden區和s0區中存活的對象則 將存活的對象直接複製到老年代 - 箭頭3。如果老年代也滿了,則會觸發一次Full GC,頻率低
如果Eden區不足以存放新建的對象,則會觸發擔保機制,將新建對象直接存儲到老年代中。