1. 程式人生 > >JVM自動記憶體管理和垃圾回收技術

JVM自動記憶體管理和垃圾回收技術

前言:java和C++之間有著記憶體自動管理和垃圾回收技術的高牆,牆裡面的人想出來,牆外面的想進去。(引用自周大師)

上一篇主要介紹了jvm的記憶體分配,沒看過的同學可以移步上篇部落格jvm記憶體分配。程式猿比較關注的記憶體的堆疊應用,這篇主要講jvm中記憶體最大的一塊區域,堆記憶體如何實現自動管理和垃圾回收。

如何判斷物件已死?

平時在開發時,我們只需new物件就可以自動分配記憶體空間,而不用去管理記憶體的銷燬回收,都是jvm幫我們做了這些工作。那麼,有個問題:在C程式中,都是程式猿去手動銷燬不使用的物件,釋放記憶體,在java中,jvm如何知道哪些是不需要使用的可以回收的物件呢?也就是怎麼判斷記憶體中物件已死?

1、引用計數

jvm會給記憶體中的物件增加一個計數器,每當物件增加一個引用,計數器就加一,當引用失效計數器就減一,計數器為零就代表這個物件是可以回收的。

缺點:無法解決迴圈引用的問題。如下圖中,ObjectA和ObjectB相互引用,ObjectA和ObjectB都無法回收。

這裡寫圖片描述

2、可達性分析演算法

從gc root物件開始,尋找引用物件,沿引用鏈,可以達到的物件都是存活的;無法達到的物件都是死亡的,可以回收的。

這裡寫圖片描述

上圖中,雖然物件D、E、F之間有引用,但和GC Root的引用鏈沒有交集,所以D、E、F物件是可以回收的物件,而物件A、B、C是活動的物件。

目前java中可作為GC Root的物件有:
1. 虛擬機器棧中引用的物件(本地變量表)
2. 方法區中靜態屬性引用的物件
3. 方法區中常量引用的物件
4. 本地方法棧中引用的物件(Native物件)

3種垃圾回收演算法

1、標記清除(Markup And Sweep)

分為兩個階段,第一階段標記階段,使用引用計數或者可達性分析標記物件是否可以回收;第二階段清除階段,將可以回收物件的記憶體回收。

缺點:會出現記憶體碎片,當建立大物件而找不到足夠大的連續的記憶體空間時會觸發Full GC,會影響整個應用程式效能。

這裡寫圖片描述

2、複製演算法(Copying)

將堆記憶體分為大小相等的兩個部分,比如A、B,當A記憶體不足時,將存活的物件拷貝到B記憶體區域,然後回收整個A記憶體區域。

缺點:記憶體使用率只有一半,十分浪費記憶體;
這裡寫圖片描述

3、標記整理

這個是針對複製演算法做的改進,解決記憶體利用率低的問題,也解決存活物件較多時,複製演算法需要大量複製物件導致的效率問題。將記憶體中存活的物件移動到記憶體區域的一端,全部移動結束後,回收分界線另一端的整個記憶體區域。

這裡寫圖片描述

分代回收

問1:jvm何時觸發gc?
答:
Minor Gc(新生代回收)的條件比較簡單,只要Eden記憶體不足就會觸發。
Full Gc(老年代回收)有幾種觸發條件:

  1. 老年代空間不足。
  2. PermSpace空間不足。
  3. 統計得到Minor Gc後晉升到老年代的平均空間大於老年代的剩餘空間。

問2:jvm如何分代?
答:
這裡寫圖片描述

檢視上圖,jvm分代主要有年輕代(young),老年代(Tenured),和非堆記憶體(永久代)。
年輕代:年輕代細分為Eden、Survivor1、Survivor2,這麼細分的作用後面講,一般新建立的物件進入年輕代,年輕代物件的特點是朝生夕死,年輕代中的物件有年齡的概念,年輕代中的gc稱為Minor Gc,每經過一次Minor Gc,物件年齡就會更加一歲,預設年齡達到15歲,物件就會進入老年代中,還有其他情況物件年齡沒有達到15歲也會進入老年代,這裡不細說了。
老年代:老年代物件的大部分是存活時間很長。
非堆記憶體(永久代):又稱方法區,主要存放類資訊、靜態變數、常量資訊。

問3:為什麼要分代回收?
答:分代回收當然是為了提升jvm回收的效率,減少因為gc導致的應用程式停頓。年輕代的物件朝生夕死,老年代的物件存活時間很長,針對不同的特點應用不同的回收演算法,年輕代應用複製演算法,因為存活物件較少,老年代因為物件存活時間較長,只能使用標記清掃或者標記整理演算法,具體要看虛擬機器產商,不同的虛擬機器產商有不同的實現。

1、年輕代

年輕代細分為Eden、Survivor1、Survivor2,採用複製演算法,因為物件朝生夕死,沒有必須按照複製演算法的一半記憶體劃分,預設的劃分比例Eden:Survivor1:Survivor2=8:1:1,為什麼分為兩個Survivor?當Eden記憶體不夠時,觸發Minor Gc,將存活的物件複製到Survivor2,然後清空Eden和Survivor1的空間,下次Minor Gc時會將存活物件複製到Survivor1,然後清空Eden和Survivor2的空間,設定兩個Survivor是為了來回騰挪存活物件使用的。

2、老年代

老年代的gc稱為Major Gc或者Full Gc,Full Gc非常消耗效能,影響應用程式,可能導致應用程式長時間停頓,jvm應該儘量避免Full Gc。避免Full Gc這涉及jvm的效能優化,後續有時間再整理分享。