天天看點

【深入淺出JVM】——垃圾回收機制

哪些記憶體需要回收?

    JVM的記憶體結構包括五大區域:程式計數器,虛拟機棧,本地方法棧,堆區,方法區。其中程式計數器,虛拟機棧,本地方法棧随線程而生,随線程而滅,是以這幾個區域的記憶體配置設定和回收都是具備确定性,不需要過多考慮回收的問題,因為方法結束或線程結束時,記憶體自然就跟随着回收 。堆區存放的是對象執行個體,也是GC回收的主要區域。

主要回收廢棄常量和無用的類

判斷廢棄常量;沒有地方再引用即為廢棄常量

無用的類:該類的所有執行個體都被回收,在Java堆中不存在無用的類

加載類的ClassLoader已經被回收

該類對應的java.lang.class對象沒有在任何地方被引用,無法在任何地方通過反射方位該類的方法

滿足上述條件可以進行類回收,但是不代表必然會被回收,在HotSpot虛拟機配置了-Xnoclassgc參數。

判斷對象是否存活

兩種方式

1、引用計數算法

每多一個地方應用,則在計數器上加1,少一個應用,計數器減1,當計數器為0時,則對象不可能再被使用。實作簡單,執行效率高,可以交織在程式中運作。

缺點:如果對象之間出現循環引用,那麼涉及到的對象将永遠都不會被銷毀。

2、可達性分析算法

通過一系列的GC Roots對象作為起始點,從這些節點向下搜尋,搜尋所走過的路徑稱為引用鍊,當一個對象到GC Roots沒有任何引用鍊相連,判斷可進行回收對象了。

GC Roots主要包含如下

虛拟機棧中引用的對象

方法區中類靜态屬性引用的對象

方法區中常量引用的對象

本地方法棧中JNI引用的對象

四種引用類型

1、強引用

代碼中Object obj = new Object(),隻要強引用存在,一定不會被回收

2、軟引用

有用但是不必需,SoftReference類實作軟引用。如果虛拟機堆記憶體足夠,則不會被回收,但記憶體空間不夠,則會被回收

使用場景

    例如部落格系統,為了提升通路性能,當使用者在點選博文時,如果文章沒有被緩存在記憶體中,則需要緩存,否則直接在記憶體中讀取,降低響應時間。可以通過資料庫級别的緩存做到這點,但是也能通過軟引用來實作。将文章用軟引用實作,如果空間足夠,則利用緩存來提升性能,如果記憶體空間不夠,可以釋放記憶體,滿足系統的需求。

3、弱引用WeakReference

弱引用 比軟引用更弱,隻能存活到下次GC之前

4、虛引用

    虛引用無論是否存在虛引用,不會對其生存時間構成影響,也無法通過虛引用來取得一個對象執行個體。設定虛引用的目的讓這個對象在被收集器回收時收到一個系統通知。擁有虛引用的對象可以在任何時候被垃圾回收器回收。

垃圾回收算法

1、标記-清除算法Mark-Sweep

将需要被回收的記憶體進行标記,然後清除标記的内容。

問題:效率低,标記和清除的效率低;會産生大量不連續的記憶體碎片

2、複制算法-Copying

将可用記憶體容量劃分為兩個相同大小的塊,每次隻是用其中一塊。當一塊記憶體用完了,就将存活的對象複制到另一塊上面,把已經用使用過的記憶體空間清理掉。

實作簡單,效率高,但是有一半的記憶體空間被浪費。

一般用于新生代在HotSpot虛拟機預設Eden和survivor為8:1

3、标記-整理算法Mark-Compact

與标記清除算法一樣,但是讓存活的對象向一端移動,直接清理掉端邊界的記憶體。

一般用于老年代,先标記後移動

4、分代收集算法

根據對象存活生命周期的不同,将記憶體劃分為幾塊,每塊采用不同的垃圾回收算法。

垃圾收集器

新生代

1、serial收集器

單線程收集器,隻會使用一個cpu或一條收集線程去完成垃圾收集工作,而且在垃圾收集的時候必須暫停所有的工作線程知道收集結束,即stop the world。

在實際中,它依舊是虛拟機運作在client模式下預設的新生代收集器。相比其他收集器:簡單而高效,對于限定單個CPU來說,Serial收集器由于沒有線程互相開銷,專心做垃圾回收。

2、parNew收集器

就是serial的多線程版本,除了使用多條線程進行垃圾收集之外,其餘行為與serial一緻。目前是運作在Server模式下的虛拟機首選新生代收集器,因為除了Serial隻有它能與CMS配合工作。但是在單CPU不會有比Serial更好的收集效果。parNew在預設情況下開啟的收集線程數與CPU的數量相同,但是可以通過使用-XX:ParallelGCThreads參數來限制。

3、parallel Scavenge收集器

新生代收集器,采用複制算法,多線程,注重吞吐量,吞吐量優先的收集器,具有自适應調節能力

需配置參數-XX:UseAdaptiveSizePolicy,就可以進行自适應。

老年代

1、serial-old收集器

标記-整理算法,serial的老年代版本,在jdk1.5以及之前的版本配合parallel scavenge收集器搭配使用。作為cms收集器的後備預案

2、paralle-old

标記-整理,在jdk1.6中開始提供,适用于注重吞吐量以及CPU資源敏感的場合。

3、CMS

标記-清除算法,擷取最短回收停頓時間為目标的收集器

GC過程短暫停,适合對時延要求較高的服務,使用者線程不允許長時間的停頓

回收流程分為4步驟

1、初始标記

隻标記GC Roots可以直接關聯到的對象,stop the world,速度非常快

2、并發标記

發生在GC Tracing的過程中(追蹤的過程中)

3、重新标記

修正并發标記期間因使用者程式繼續運作而導緻标記産生變動的那一部分對象的标記記錄,stop the world

4、并發清除

耗時比較長的階段為并發标記和并發清除

CMS的缺點

1、CMS對CPU敏感,會占用cpu資源導緻程式變慢,預設啟動的回收線程數是(cpu數量+3)/5,如果cpu個數過少,會導緻程式更慢。

2、CMS無法處理浮動垃圾,可能Concurrent mode failure失敗,導緻另一GC

1.6中cms預設當老年代使用92%進行,可以通過配置-XX:CMSInitiatingOccupancyFraction的值來修改,如果設定過高,會導緻大量Concurrent mode failure,會到臨時啟用Serial Old 性能反而下降。

3、标記-清除算法,會産生大量碎片,如果無法找到足夠連續空間來配置設定目前對象,會提前觸發Full GC。為了解決,配置-XX:+UseMSCompactAtFullCollection開關參數,預設開啟,用于在進行FullGC的時候開啟記憶體碎片的合并整理過程,通過配置參數-XX:CMSFullGCsBeforeCompaction,用于設定執行多少次不壓縮FullGC後,跟着來一次帶壓縮的。

4、G1收集器

面向服務端應用的垃圾收集器,采用标記-整理算法,目标替換CMS。

特點:

1、并行與并發

2、分代收集

3、空間整合,标記-整理算法

4、可預測停頓。

運作步驟

1、初始标記 stop the world

2、并發标記

3、最終标記stop the world

4、篩選回收 stop the world

優點:利用多cpu,多核環境下的硬體優勢,縮短stop the world時間

記憶體回收流程

JVMHeap區和方法區結構圖

【深入淺出JVM】——垃圾回收機制

Heap區GC流程

1、新建立的對象會先進入Eden區(如果對象過大也會直接進入Old區)。

2、GC之前對象存在Eden和From區,進行GC的時候Eden中的對象被拷貝到To這樣一個survive空間(包括from和to,它們兩個空間大小一緻,又稱為s1和s2)中,From中的對象GC幸存一定的次數(每次GC之後這個對象依舊存在,GC一次Age就加1,預設15)後進入Old區,除此之外還有一種額外的可能性,進行GC的時候會對survive中的對象進行判斷,Survive空間中存在一些對象Age一樣,而且年齡相同的這一部分空間占用大于Survivor,這組對象就會進入Old區。當Eden複制到To區的時候,To空間不足夠的時候也會直接進入到Old區。

3、進入To區或Old區之後,Eden和From中無效的對象就會通過MinorGC進行回收。

4、當Old區的空間占滿後,就會進行Full GC。

方法區GC流程

類的執行個體對象全部被GC了,同時它的類加載也被GC了,那麼方法區中對象将會進行GC。

一般引發Full GC的原因主要有

from survive中對象的生命周期到一定的門檻值

配置設定的對象都是大對象

To 空間的不夠,進行GC直接把對象拷貝的Old區。

注意:

如果Young Generation大小配置設定不合理或空間比較小,這個時候導緻對象很容易進入Old Generation中,而Old Generation中回收具體對象的時候速度是遠遠低于Young Generation回收速度。

是以實際配置設定要考慮年老代和新生代的比例,考慮Eden和survives的比例