(一)學習JVM ——運行時數據區域 java
(二)學習JVM —— 垃圾回收機制算法
在Java堆中存放着Java中幾乎全部的對象實例,垃圾收集器在對堆進行回收前,第一件事情就是肯定這些對象哪些還存活着,哪些已經可回收。框架
Java採用可達性分析來斷定對象是不是存活的,這個算法的基本思路就是經過一系列稱爲"GC Root"的對象做爲起始點,從這些節點向下搜索,走過引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時,則證實這個對象已經能夠回收。學習
Java中可做爲GC Root的對象有四種:spa
引用的定義是:若是reference類型的數據中存儲的數值表明的是另外一塊內存的起始地址,就稱這塊內存表明着一個引用。.net
Java堆引用概念進行了擴展,分爲下述4種:線程
真正標記一個對象爲可回收,至少要經歷兩次標記過程:代理
在經歷可達性分析後,發現沒有與GC Root關聯,會被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。對沒有覆蓋或者已經調用過finalize()方法的對象,JVM都不會去執行finalize()方法了。
若是對象有必要執行finalize()方法,那麼該對象會被放置到F-Queue隊列,並在稍後由一個自動創建的,低優先級的Finalizer線程去執行它。
JVM的共享內存分爲堆和方法區,有不少人認爲方法區(或永久代)是沒有垃圾回收的,JVM規範也確實說過能夠不對方法區進行回收,由於性價比較低,通常在堆中,尤爲是新生代中,常規應用進行一次垃圾回收能夠回收70%~95%的空間,而方法區則否則。
方法區若是進行垃圾收集,主要回收廢棄常量和無用的類。
斷定常量是不是廢棄的常量,是指進入了常量池,可是系統中沒有任何一個地方引用了這個字面量,就能夠回收。
斷定一個類是不是一個無用的類的條件比較苛刻,有3個條件:
是否對類進行回收,能夠用-Xnoclassgc參數進行控制,還能夠在Product版虛擬機中,用-verbose:class以及-XX:+TraceClassLoading查看類加載和卸載信息。在FastDebug版虛擬機中用-XX:+TraceClassUnLoading參數查看。
在大量使用反射、動態代理、CGLib等ByteCode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都須要虛擬機具有類卸載的功能,以保證方法區不會溢出。
垃圾回收的經常使用算法有三種,分別是標記&清除算法、複製算法和標記&整理算法。
標記&清除(Mark&Sweep)分爲兩個階段,標記和清除。首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象,該算法的主要弊端在於,清除以後會產生大量不連續的內存碎片,碎片太多會致使之後在運行過程當中須要分配較大對象時,沒法找到連續的內存空間,而不得不提早出發另外一次垃圾收集。
複製(Copying)算法,能夠將內存按照容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊用完了,就將還存活着的對象複製到另外一塊,而後把已經使用過的內存一次清理掉。只是這種作法將內存縮小到了原來的一半,成本高了一點。
根據IBM公司專門研究代表,新生代中有98%的對象時朝升西落的,因此,並不須要按照1:1的比例來劃份內存空間,而是將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一個Survivor空間。
當回收時,將Eden和Survivor中還存活的對象,一次性的複製到另外一塊Survivor中,而後清理掉Eden和剛纔使用的那個Survivor空間。JVM默認採用8:1:1的方式分配Eden和兩個Survivor。
當複製過程當中發生Survivor不夠用時,須要依賴其餘內存(這裏指老年代)進行分配擔保(Handle Promotion),將這些對象直接分配到老年代。
標記&整理(Mark&Compact),標記過程與標記清理同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
當前商業虛擬機的垃圾收集都採用分代收集(Generational Collection),根據對象存活週期的不一樣將內存劃分爲幾塊。通常是把Java堆分紅新生代和老年代。
新生代對象存活率低,選用複製算法,將內存以8:1:1分配。
老年代對象存活率高,沒有額外空間對它進行分配擔保,因此採用標記&清理,或標記&整理算法。
前面從理論層面介紹了對象存活的判斷方式和垃圾回收算法,而JVM在實現這些算法時,必須對算法的執行效率有嚴格的考量,才能保證JVM高效運行。
JVM在進行可達性分析時,並不會真的從每個GC Root進行引用鏈檢查,如今不少應用僅僅是方法區就有數百兆,若是要逐一檢查,必然會消耗不少時間。
另外,可達性分析還體如今GC停頓上,爲了確保分析準備,不能夠出如今分析過程當中對象引用關係還在不斷變化的狀況,GC停頓還被Sun公司成爲稱爲Stop The World。
HotSpot實現中,使用一組OopMap的數據結構來幫助快速檢查引用鏈,在類加載完成時,HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來,在JIT編譯過程當中,也會在特定的位置記錄棧河寄存器中那些位置是引用。這樣,GC在掃描時就能夠直接得知這些信息了。
可是可能致使引用關係變化的指令很是多,若是爲每一條都生成對應的OopMap,那將須要大量額外的空間,這樣GC的成本會變得很高。下面要介紹的安全點則解決了這個問題。
程序執行時並不是在全部位置均可以停下來GC,只有在達到安全點(Safepoint)時才能暫停。
安全點的選定不能太多,也不能太少,它的選定基本上是以程序「是否具備讓程序長時間執行的特徵」爲標準來選定的,長時間執行最明顯的特徵就是指令序列複用,例如方法調用、循環跳轉、異常跳轉等,具備這些功能的指令纔會產生安全點。
另外一個要考慮的問題是,如何在GC發生時,讓全部線程都跑到最近的安全點停頓下來。
目前,基本上全部的虛擬機都採用主動式中斷的思想,當GC須要中斷線程的時候,輪詢一個標誌,發現中斷標誌爲true時,就本身中斷掛起,輪詢標誌的地方河安全點是重合的。
程序不執行的時候,就是沒有分配CPU時間時,線程沒法響應JVM的中斷請求,這時就須要安全區域(Safe Regin)來解決。
安全區域是指一段代碼片斷中,引用關係不會發生變化。在這個區域GC都是安全的。
線程進入安全區域時會進行標記,離開時要檢查本身是否完成了根節點枚舉,若是完成了就繼續工做,不然要等到能夠安全離開的信號爲止。
(二)學習JVM —— 垃圾回收機制