1. 程式人生 > >深入理解java虛擬機器-垃圾收集器與記憶體分配策略

深入理解java虛擬機器-垃圾收集器與記憶體分配策略

開發十年,就只剩下這套架構體系了! >>>   

垃圾收集器與記憶體分配策略

引用計數法

就是給物件新增一個計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1,計數器為0的物件就是不可能再被使用的。

缺點:無法解決物件之間迴圈引用問題。

可達性分析演算法

通過GC Roots的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,如果物件到GC Roots沒有任何一條引用鏈相連的化,說明物件不可用,會被判定為可回收物件。

 

 

作為GC Roots的物件:

  1. 虛擬機器棧(棧幀中的本地變量表)中引用的物件
  2. 方法區中類靜態屬性引用的物件
  3. 方法區中常量引用的物件
  4. 本地方法棧中JIT(Native方法)引用的物件

 

再談引用

Jdk1.2以前,如果reference型別資料中儲存的數值代表的是另外一塊記憶體的起始地址,就稱這塊記憶體代表值一個引用。

Jkd1.2以後,將引用分為強引用,軟引用,弱引用,虛引用4種。

  1. 強引用:通過new出來的物件
  2. 軟引用:有用,非必須,提供SoftReference類來實現軟引用
  3. 弱引用:非必須物件,提供WeakReference來實現弱引用。
  4. 虛引用:唯一目的是在這個物件被回收會有通知,提供PhantomReference來實現。

 

生存還是死亡

宣告物件死亡,至少要經過兩次標記,第一是是否可達,第二是是否執行finalize()方法。

 

回收方法區

永久代的垃圾回收主要有兩部分內容:廢棄常量和無用的類。

判斷無用的類需要滿足:

  1. 該類所有的例項都已經被回收,也就是Java堆中不存在該類的任何例項
  2. 載入該類的Class Loader已經被回收
  3. 該類對應的Class物件沒有被引用,無法通過反射機制訪問該類的方法。

 

垃圾收集演算法

標記-清除演算法

標記所有需要回收的物件,再統一清除。

不足點:效率低,碎片問題(分配大物件沒有可用的連續空間)

 

 

 

複製演算法

記憶體分兩塊,一塊使用,一塊用於複製

優點:無碎片化,實現簡單,執行高效

缺點:導致使用的記憶體縮小為原來一半,效率在物件存活較多時進行較多複製操作較低

應用場景:

結合複製演算法演變到將記憶體分為新生代和老年代(Eden區,s0,s1區)8:1:1,也就是說每次新生代可用記憶體空間90%,大大提高記憶體利用率,當s空間不夠時,需要依賴其它記憶體(老年代)進行分配擔保。

標記-整理演算法

老年代特點,回收次數少,讓存活的物件都移動到一端,然後再清理掉端邊界以外的記憶體。

 

分代收集演算法

商業虛擬機器採用的垃圾收集演算法,原理是:根據物件存活週期不同將記憶體劃分,分代的目的是為了更好地回收記憶體,比如,新生代採用複製演算法,老年代採用標記整理演算法。

 

HotSpot的演算法實現

列舉根節點

當系統停頓下來時,不需要檢查所有執行上下文和全域性引用位置,虛擬機器知道那些地方存在物件引用,在HotSpot實現中,通過OopMap的資料結構來實現,在類載入完成的時候,HotSpot就把物件內偏移量對應的資料型別的資料計算出來。在JIT編譯過程中,也會記錄下棧和暫存器中哪些位置是引用,這樣,GC在掃描時就可以直接得知這些資訊。

安全點

如果為每一條指令都生成對應的OopMap,將會需要大量的空間。GC成本也會提高,事實上,GC只是在特定的位置記錄下這些資訊,這些位置稱為安全點;

搶佔式中斷和主動式中斷。

安全區域

在程式不執行的時候。也就是沒有分配CPU時間,比如執行緒處於sleep狀態或者Blocked狀態,這時候執行緒無法響應JVM的中斷請求,這個時候就需安全區域來解決。

 

垃圾收集器

記憶體回收的具體實現

 

Serial收集器

單執行緒,使用者執行緒停頓,簡單高效(與其它單執行緒收集器比較)

ParNew收集器

是Serial收集器多執行緒版本,其控制引數,收集演算法,物件分配規則,回收策略等都和Serial收集器一樣,關鍵是除了Seria收集器外只有它能與CMS收集器(併發收集器,老年代收集器)配合工作。

Paraller Scavenge收集器

新生代收集器,也是使用複製演算法的收集器,並行多執行緒收集器,目的是為了達到一個可控制的吞吐量。所謂吞吐量就是CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值,即吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)。,更高的吞吐量可以高效利用CPU時間,儘可能完成程式的運算任務,適合互動不多的任務。

Serial Old收集器

是Serial收集器老年代版本,單執行緒,使用標記整理演算法

Parallel Old收集器

是Paraller Scavenge老年代版本收集器,多執行緒,標記-整理演算法,在注重吞吐量優先以及CPU資源敏感場合,優先考慮Parallel Scavenge加Parallel Old收集器

 

CMS收集器

  1. 以獲取最短回收停頓時間為目標的收集器。在重視服務響應速度上,給使用者帶來較好體驗。
  2. 基於標記-清除演算法實現,過程包括:
  3. 初始標記,併發標記,重新標記,併發清除、
  4. 初始標記僅僅只是標記一下GC Roots能直接關聯到的物件,速度很快,併發標記階段就是進行GC Roots過程,而重新標記則是為了修正併發標記期間使用者程式繼續運作而導致標記產生變動的哪些物件。
  5. 在併發標記與併發清除過程是和使用者執行緒一起工作。
  6. 併發收集,低停頓

缺點:

  1. 對CPU資源敏感
  2. 無法處理浮動垃圾
  3. 演算法本身會帶來空間碎片問題

 

G1收集器

面向服務端應用的垃圾收集器

具備以下特點:

  1. 併發與並行:充分利用CPU,多核環境硬體優勢,使用CPU來縮短停頓時間。
  2. 分代收集:獨立管理整個GC堆
  3. 空間整理:採用標記-整理演算法,不會產生空間碎片問題
  4. 可預測的停頓:可預測停頓模型,可自己指定垃圾收集時間範圍

 

G1將整個Java堆劃分為多個大小相同的獨立區域(Region),保留新生代和老年代的概念,但他們物理不是隔離的,都是一部分獨立區域的集合。

G1收集器根據每個Region區域裡面的垃圾堆價值大小,在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region。這種使用Region劃分記憶體空間以及優先順序的區域回收方式,保證G1收集器在有限時間內可以獲取更高的收集效率。

G1把記憶體化整為0。

 

記憶體分配與回收策略

物件優先在Eden區分配

大多數情況下,物件在新生代Eden區中分配,當Eden區沒有足夠空間進行分配時,虛擬機器就會發起一場Minor GC

大物件直接進入老年代

大物件:需要大量連續記憶體空間的Java物件,比如很長的字串以及陣列,大物件直接在老年代分配,目的是為了避免在Eden區以及兩個Survivor區之間發生大量的記憶體複製(複製演算法)。

長期存活的物件將進入老年代

虛擬機器給每個物件定義了一個物件年齡(Age)計數器。如果物件在Eden出生並經過第一次Minor GC後仍然存活,並且能被Survivor容納的化,將會被移動到Survivor空間中,物件年齡設為1,物件在Survivor區中每熬過一次Minor GC 年齡就加1,當它的年齡增加到15歲(預設)將會被晉升到老年代中。

動態年齡判定

為了適應不同程式記憶體狀況,物件年齡不是必須達到15才可以晉升到老年代,如果在Survivor空間中相同年齡所有物件大小總和大於Survivor空間一半,年齡大於或等於該年齡的物件就可以直接進入老年代。

空間分配擔保

在發生Minor GC之前,虛擬機器會先檢查老年代最大可用的連續空間是否大於新生代所有物件總空間,如果成立,那麼Minor GC是安全的,不成立,虛擬機器會檢視設定值是否允許擔保失敗,如果允許,會繼續檢查老年代最大可用連續空間是否大於歷代晉升到老年代物件平均大小,如果大於,就可用嘗試進行一次Minor GC,如果小於,或者設定為不允許冒險,