GC 即 Garbage Collection,中文 意思「垃圾回收」,在有GC以前,咱們手動去管理內存,若是你忘記標記某一處已經再也不使用的內存,那麼這塊內存將永遠不會被系統回收,也就是常說的 「內存泄露」。java
如下全部的 GC 介紹,所有基於主流 JVM 虛擬機 Hotspot。數組
GC判斷一個對象存活或死亡就是判斷這個對象還存不存在它的引用,常見的兩種方式以下安全
每一個對象從建立開始,都會維護一個引用計數器,每當引用一次,那麼計數器增長1,引用失效一次,那麼計數器減去1,這樣實現優勢是高效、簡單,可是缺點也很明顯:沒法解決循環依賴,好比下面的代碼,雖然 A引用B,B引用A,可是就沒有其餘地方引用了,所以它們是無效引用,形成內存泄露。Java 天然不會選擇這種方式做爲判斷方式。bash
A=B
B=A
複製代碼
將一系列的 GC Roots 對象做爲起點,開始向下搜索。可做爲 GC Root 的起點有測試
Java 虛擬機棧(棧楨本地變量表中)引用的對象spa
本地方法棧中JNI(也就是常說的 Native 方法)線程
方法中的常量、類靜態屬性引用的對象翻譯
注意:向下搜索的路徑就是引用鏈code
爲了方便理解,我畫了下面的圖片cdn
特別注意: 可達性分析僅僅是判斷對象是否可達,但還不足以判斷對象是否存活或者死亡。可達性分析中判斷爲不可達的對象,只是被判刑 ≠ 死亡。
不可達對象會存放在 「即將回收」集合中,要判斷一個對象是否真正的死亡,還須要通過下面的兩個步驟。
可達性分析中標記爲不可達的對象,會經歷第一次篩選。
篩選標準:判斷對象是否須要執行 finalize() 方法,如有必要執行,則篩選進行下一階段分析,若不必執行,那麼該對象斷定爲死亡,不篩選,等待系統回收。
當對象無 finalize() 方法,或者 finalize() 方法已經被虛擬機調用過一次的狀況下,那麼被標記爲不必執行,等待回收。
當對象通過了第一次篩選後沒有被回收,將進行第二次篩選。
該對象會被放在一個 F-Queue
的隊列中,並由虛擬機自動建立一個名爲 Finalizer 的低優先級的線程去執行隊列中全部對象的 finalize 方法,這裏須要注意的是,finalize 方法只會被執行一次,而且 JVM 並不承諾 finalize 會執行完畢,緣由是爲了防止 finalize 執行時間過長或者中止執行,致使的內存溢出。
篩選標準: 在執行 finalize 方法的過程當中,若是該對象依舊沒有和 GC Root 關聯起來,那麼該對象被判斷爲死亡,留在即將回收集合,等待回收。
簡單理解就是,全局GC(Full GC)和局部GC(Partial GC),分別看一下:
Young GC :只收集 Young Gen(年輕代)的 GC, Young GC 還有一個叫法 叫 Minor GC。
old GC : 只收集 Old Gen(年老代) 的GC 只有垃圾回收器 CMS 的 concament colletton 有這個模式。
mixed GC : 收集整個Young GC的GC和部分的old Gen的GC,只有垃圾回收器 G1 有這個模式。
Full GC是對整個堆來講的,執行Full GC 的時候會回收全部代,包括永久代、年老代、年輕代等等全部的 GC。Full GC 的觸發條件有如下幾種
此方法的調用是建議JVM進行Full GC,雖然只是建議而非必定,但不少狀況下它會觸發 Full GC,從而增長Full GC的頻率,也即增長了間歇性停頓的次數。強烈建議能不使用此方法就別使用,讓虛擬機本身去管理它的內存,可經過經過-XX:+ DisableExplicitGC來禁止RMI(Java遠程方法調用)調用System.gc。
舊生代空間只有在新生代對象轉入及建立爲大對象、大數組時纔會出現不足的現象,當執行Full GC後空間仍然不足,則拋出以下錯誤: java.lang.OutOfMemoryError: Java heap space 爲避免以上兩種情況引發的FullGC,調優時應儘可能作到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要建立過大的對象及數組。
JVM規範中運行時數據區域中的方法區,在HotSpot虛擬機中又被習慣稱爲永生代或者永生區,Permanet Generation中存放的爲一些class的信息、常量、靜態變量等數據,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被佔滿,在未配置爲採用CMS GC的狀況下也會執行Full GC。若是通過Full GC仍然回收不了,那麼JVM會拋出以下錯誤信息: java.lang.OutOfMemoryError: PermGen space 爲避免Perm Gen佔滿形成Full GC現象,可採用的方法爲增大Perm Gen空間或轉爲使用CMS GC。
若是發現統計數聽說以前Minor GC的平均晉升大小比目前old gen剩餘的空間大,則不會觸發Minor GC而是轉爲觸發full GC
爲了方便理解上述枯燥又無味的理論,我寫了幾行代碼,實驗了一下
/** * GCRoots 測試:虛擬機棧(棧幀中的局部變量)中引用的對象做爲GCRoots * -Xms1024m -Xmx1024m -Xmn512m -XX:+PrintGCDetails * <p> * 擴展:虛擬機棧中存放了編譯器可知的八種基本數據類型,對象引用,returnAddress類型(指向了一條字節碼指令的地址) * * @author wangjie */
public class TestGCRoots01 {
private int _10MB = 10 * 1024 * 1024;
private byte[] memory = new byte[8 * _10MB];
public static void main(String[] args) {
method01();
System.out.println("返回main方法");
System.gc();
System.out.println("第二次GC完成");
}
public static void method01() {
TestGCRoots01 t = new TestGCRoots01();
System.gc();
System.out.println("第一次GC完成");
}
}
複製代碼
當運行上述代碼控制檯會輸出以下內容
首先該類聲明瞭一個 80M 的數組
private byte[] memory = new byte[8 * _10MB];
複製代碼
而後調用了 method01,method01建立了一個TestGCRoots01的實例,該實例存放在 PSYoungGen 也就是年輕代
method01();
TestGCRoots01 t = new TestGCRoots01();
複製代碼
這時調用了 System.gc(),該實例從PSYoungGen到了ParOldGen也就是年老代
System.gc();
System.out.println("第一次GC完成");
複製代碼
可是並無被回收,method01執行完畢後返回到了 main 方法,這時又執行了一次 System.gc(),該實例被回收 。
System.out.println("返回main方法");
System.gc();
System.out.println("第二次GC完成");
複製代碼
至此,GC 的基礎知識你應該瞭解了,可是這篇文章僅僅簡單分析了一下 GC 和 JVM 的關係,並不涉及到引用鏈,若是對你理解 GC 有幫助,點贊轉發是對我最大的支持。
另外,我爲你找到了如下資料,並翻譯成了中文供你查閱