(二)學習JVM —— 垃圾回收機制

(一)學習JVM ——運行時數據區域 java

(二)學習JVM —— 垃圾回收機制算法

(三)學習JVM —— 垃圾回收器 安全

(四)學習JVM —— 內存分配與回收策略 數據結構

對象什麼時候可被回收?

在Java堆中存放着Java中幾乎全部的對象實例,垃圾收集器在對堆進行回收前,第一件事情就是肯定這些對象哪些還存活着,哪些已經可回收。框架

可達性分析

Java採用可達性分析來斷定對象是不是存活的,這個算法的基本思路就是經過一系列稱爲"GC Root"的對象做爲起始點,從這些節點向下搜索,走過引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時,則證實這個對象已經能夠回收。學習

Java中可做爲GC Root的對象有四種:spa

  1. 虛擬機棧中引用的對象;
  2. 方法區中靜態屬性引用的對象;
  3. 方法區中常量引用的對象;
  4. 本地方法棧中JNI(Native方法)引用的對象;

引用的種類

引用的定義是:若是reference類型的數據中存儲的數值表明的是另外一塊內存的起始地址,就稱這塊內存表明着一個引用。.net

Java堆引用概念進行了擴展,分爲下述4種:線程

  • 強引用(Strong Reference):只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象;
  • 軟引用(Soft Reference):將要發生內存溢出以前,將會把這些對象列入回收範圍之中進行第二次回收。若是此次回收尚未足夠的內存,纔會內存溢出;
  • 弱引用(Weak Reference):弱引用只能生存到下一次垃圾回收發生以前。
  • 虛引用(Phantom Reference):虛引用(幻影引用)惟一的目的就是能在這個對象被回收時收到一個系統通知。

回收前的細節

真正標記一個對象爲可回收,至少要經歷兩次標記過程:代理

在經歷可達性分析後,發現沒有與GC Root關聯,會被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。對沒有覆蓋或者已經調用過finalize()方法的對象,JVM都不會去執行finalize()方法了。

若是對象有必要執行finalize()方法,那麼該對象會被放置到F-Queue隊列,並在稍後由一個自動創建的,低優先級的Finalizer線程去執行它。

方法區怎麼回收?

JVM的共享內存分爲堆和方法區,有不少人認爲方法區(或永久代)是沒有垃圾回收的,JVM規範也確實說過能夠不對方法區進行回收,由於性價比較低,通常在堆中,尤爲是新生代中,常規應用進行一次垃圾回收能夠回收70%~95%的空間,而方法區則否則。

方法區若是進行垃圾收集,主要回收廢棄常量和無用的類。

斷定常量是不是廢棄的常量,是指進入了常量池,可是系統中沒有任何一個地方引用了這個字面量,就能夠回收。

斷定一個類是不是一個無用的類的條件比較苛刻,有3個條件:

  1. 該類全部的實例已經被回收,堆中不存在該類的任何實例;
  2. 加載該類的ClassLoader已經被回收;
  3. 該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法;

是否對類進行回收,能夠用-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分配。

老年代對象存活率高,沒有額外空間對它進行分配擔保,因此採用標記&清理,或標記&整理算法。

HotSpot虛擬機回收算法的實現

前面從理論層面介紹了對象存活的判斷方式和垃圾回收算法,而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 ——運行時數據區域

(二)學習JVM —— 垃圾回收機制

(三)學習JVM —— 垃圾回收器

(四)學習JVM —— 內存分配與回收策略

相關文章
相關標籤/搜索