在瞭解垃圾回收算法以前,先熟悉何爲垃圾?對象存放在內存中,內存又是電腦中的寶貴資源,當內存不夠用時,就須要把這些內存中死對象進行回收,釋放內存。html
判斷內存中對象是否存活有兩種方法:java
1. 引用計數法算法
這種方法在對象有引用的時候,也就是引用計數大於0,在對象的的引用頭裏面添加引用的次數,可是存在對象互相引用的死循環,致使內存沒法被回收。網絡
例如,jvm
public class GcDemo { public static void main(String[] args) { //分爲6個步驟 GcObject obj1 = new GcObject(); //Step 1 GcObject obj2 = new GcObject(); //Step 2 obj1.instance = obj2; //Step 3 obj2.instance = obj1; //Step 4 obj1 = null; //Step 5 obj2 = null; //Step 6 } } class GcObject{ public Object instance = null; }
2. 可達性判斷(根搜索法判斷)url
根搜索算法是以「GC ROOTS」爲起始點往下搜索,全部通過的對象合併起來稱爲引用鏈,在這引用鏈裏,沒有的對象稱爲垃圾對象,(實際上jvm還作了一個篩選動做,斷定當前對象是否執行finalize()方法,若是不須要執行才斷定爲垃圾對象,這裏不作介紹),在引用鏈裏的是活躍對象。那什麼樣的對象才能稱爲「GC ROOTS」呢?如下四種能夠spa
虛擬機棧(棧幀中的本地變量表)中引用的對象。方法區中的類靜態屬性引用的對象。方法區中的常量引用的對象。本地方法棧中 JNI(Native 方法)的引用對象。.net
什麼狀況下回收對象
即便在可達性分析算法中不可達的對象,也並不是是「非死不可」,這時候它們暫時處於「緩刑」階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程。指針
第一次標記:若是對象在進行可達性分析後發現沒有與GC Roots相鏈接的引用鏈,那它將會被第一次標記;code
第二次標記:第一次標記後接着會進行一次篩選,篩選的條件是此對象是否有必要執行finalize()
方法。在finalize()
方法中沒有從新與引用鏈創建關聯關係的,將被進行第二次標記。
第二次標記成功的對象將真的會被回收,若是對象在finalize()
方法中從新與引用鏈創建了關聯關係,那麼將會逃離本次回收,繼續存活。
java垃圾回收算法有四種:
1 標記-清除算法
標記-清除算法採用從根集合(GC Roots)進行掃描,對存活的對象進行標記,標記完畢後,再掃描整個空間中未被標記的對象,進行回收,以下圖所示。標記-清除算法不須要進行對象的移動,只需對不存活的對象進行處理,在存活對象比較多的狀況下極爲高效,但因爲標記-清除算法直接回收不存活的對象,所以會形成內存碎片。
存在缺點:
1. 標記和清除的過程效率不高
2. 空間產生碎片化
3. 須要使用空閒鏈表 (free.list),來記錄全部的空閒區域,以及每一個區域的大小。維護空閒表增長了對象分配時的開銷。
4. 與寫時複製技術不兼容
2 複製算法
複製算法的提出是爲了克服句柄的開銷和解決內存碎片的問題。它開始時把堆分紅 一個對象空間和多個空閒空間,程序從對象面爲對象分配空間,當對象滿了,基於複製算法的垃圾收集就從根集合(GC Roots)中掃描活動對象,並將每一個活動對象複製到空閒空間(使得活動對象所佔的內存之間沒有空閒洞),這樣空閒空間變成了對象空間,原來的對象空間變成了空閒空間,程序會在新的對象空間中分配內存。
3 標記-整理算法
標記-整理算法採用標記-清除算法同樣的方式進行對象的標記,但在清除時不一樣,在回收不存活的對象佔用的空間後,會將全部的存活對象往左端空閒空間移動,並更新對應的指針。標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,所以成本更高,可是卻解決了內存碎片的問題
4 分代收集算法
分代收集算法是目前大部分JVM的垃圾收集器採用的算法。它的核心思想是根據對象存活的生命週期將內存劃分爲若干個不一樣的區域。通常狀況下將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),在堆區以外還有一個代就是永久代(Permanet Generation)。老年代的特色是每次垃圾收集時只有少許對象須要被回收,而新生代的特色是每次垃圾回收時都有大量的對象須要被回收,那麼就能夠根據不一樣代的特色採起最適合的收集算法。
整理於網絡: