“阿里架構師”的JVM之GC詳解

認真期待發表於2018-05-08

GC的概念

Grabage Collection:在系統執行過程中佔據空間的無用物件在一定時間範圍內被及時清理來保證整個系統有足夠的記憶體空間來執行。java中GC的物件是堆和永久區。

常用的GC演算法

引用計數法(reference counting)

概念:對於一個物件A,只要有任何一個物件引用了A,則A的引用計數器就加1,當引用失效時,引用計數器就減1。只要物件A的引用計數器的值為0,則物件A就不可能再被使用。

出現的問題:①.引用和去引用伴隨加法和減法,影響效能。②.很難處理垃圾物件的迴圈引用。下面一段程式碼說明迴圈引用問題:

 複製程式碼
package jvm;/** * GC引用計數法Demo * @author Administrator * */public class JvmGcDemo { public static void main(String[] args) { GcObject g1 = new GcObject();//棧中建立變數g1指向(引用)new出物件GcObject1(堆中),此時GcObject1的引用計數為1 GcObject g2 = new GcObject();//棧中建立變數g2指向(引用)new出物件GcObject2(堆中),此時GcObject2的引用計數為1 g1.o = g2;//GcObject1的引用計數再加1,引用計數=2 g2.o = g1;//GcObject2的引用計數再加1,引用計數=2 g1 = null;//GcObject1的引用計數減1,引用計數=1 g2 = null;//GcObject2的引用計數減1,引用計數=1 } } class GcObject{ public Object o; }1234567891011121314151617181920複製程式碼

看上面程式碼流程發現GcObject1和GcObject2迴圈引用,引用計數都為1,最後引用計數法無法回收這兩個無用的物件。

在eclipse中初始化配置jvm引數,最大使用記憶體20M,最小使用記憶體5M:-Xmx20m -Xms5m;修改上段程式碼,用System.gc()進行GC,即下面程式碼:

 複製程式碼
package jvm;/** * GC引用計數法Demo * @author Administrator * */public class JvmGcDemo { public static void main(String[] args) { printMemory(1);//列印記憶體 free memory:4.719520568847656M,約5M GcObject g1 = new GcObject();//棧中建立變數g1指向(引用)new出物件GcObject1(佔用1M堆記憶體),此時GcObject1的引用計數為1 GcObject g2 = new GcObject();//棧中建立變數g2指向(引用)new出物件GcObject2(佔用1M堆記憶體),此時GcObject2的引用計數為1 printMemory(2);//列印記憶體發現free memory:2.7194900512695312M = 4.719520568847656M - 2M g1.o = g2;//GcObject1的引用計數再加1,引用計數=2 g2.o = g1;//GcObject2的引用計數再加1,引用計數=2 g1 = null;//GcObject1的引用計數減1,引用計數=1 g2 = null;//GcObject2的引用計數減1,引用計數=1 System.gc();//進行GC printMemory(3);//free memory3:4.922454833984375M 說明GcObject1和GcObject2被回收 } /** * 列印記憶體 */ public static void printMemory(int i){ //列印空閒堆記憶體 System.out.println("free memory"+i+":"+Runtime.getRuntime().freeMemory()/1024.0/1024+"M"); System.out.println("---------------------------->"); } } class GcObject{ public Object o; byte[] by = new byte[1024*1024];//佔用1M記憶體}1234567891011121314151617181920212223242526272829303132複製程式碼

控制檯輸出:

free memory1:4.75958251953125M

—————————->

free memory2:2.7194137573242188M

—————————->

free memory3:4.922454833984375M

—————————->

發現GC後free memory3比free memory2增加了2M多一點(多出的部分是回收的其他垃圾記憶體),free memory3與free memory1無明顯差別,說明物件GcObject1和GcObject2被回收,這是因為JVM沒有采用引用計數法進行回收垃圾。

標記-清除演算法(mark-and-sweep)

概念:標記-清除演算法是現代垃圾回收演算法的思想基礎。是指在可以使用的記憶體被耗盡的時候,GC執行緒就會被觸發並將程式暫停,進行標記-清除演算法將垃圾回收,過程分為兩個階段:標記階段和清除階段。在標記階段,首先通過根節點,標記所有從根節點開始的可達物件。因此,未被標記的物件就是未被引用的垃圾物件。然後,在清除階段,清除所有未被標記的物件。

出現的問題:①.標記和清除過程效率不高 ,而且在進行GC的時候,需要停止應用程式,這會導致使用者體驗非常差勁。②.標記清除之後會產生大量不連續的記憶體碎片。失效物件都是隨即的出現在記憶體的各個角落的,現在把它們清除之後,記憶體的佈局自然會亂七八糟。

“阿里架構師”的JVM之GC詳解

標記-壓縮演算法(mark-compact)

概念:標記-壓縮演算法適合用於存活物件較多的場合,如老年代。它在標記-清除演算法的基礎上做了一些優化。和標記-清除演算法一樣,標記-壓縮演算法也首先需要從根節點開始,對所有可達物件做一次標記。但之後,它並不簡單的清理未標記的物件,而是將所有的存活物件壓縮到記憶體的一端,之後,清理邊界外所有的空間。

出現的問題:標記-壓縮演算法的缺點就是效率也不高,不僅要標記所有存活物件,還要整理所有存活物件的引用地址。

“阿里架構師”的JVM之GC詳解

複製演算法(copying)

概念:將原有的記憶體空間分為兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的記憶體中的存活物件複製到未使用的記憶體塊中,之後,清除正在使用的記憶體塊中的所有物件,交換兩個記憶體的角色,完成垃圾回收.

出現的問題:用空間換取時間,空間浪費,不適用於存活物件較多的場合,如老年代等。

“阿里架構師”的JVM之GC詳解

分代收集演算法(generational collecting)

概念:把物件分為年輕代、年老代、持久代,對不同的生命週期使用不同的演算法進行回收,一般新生代物件和小物件適合複製演算法,老年代物件和大物件存活適合標記-清理或者標記-壓縮演算法。

“阿里架構師”的JVM之GC詳解

“阿里架構師”的JVM之GC詳解

物件的可觸及性

可觸及的

從根節點可以觸及到這個物件。根一般為棧中引用的物件、方法區中靜態成員或者常量引用的物件(全域性物件)、JNI方法棧中引用物件。

可復活的

一旦所有引用被釋放,就是可復活狀態,因為在finalize()中可能復活該物件。

不可觸及的

在finalize()之後,可能會進入不可觸及狀態,不可觸及的物件不可能復活,可以回收。

GC引數

序列收集器

最古老、最穩定、效率高的收集器,缺點是可能會產生較長時間的停頓,她只使用一個執行緒去回收,沒有辦法發揮多核計算機的效能。

-XX:+UseSerialGC

新聲代和老年代使用序列回收,新生代使用複製演算法、老年代使用標記-壓縮演算法。

“阿里架構師”的JVM之GC詳解

並行收集器

ParNew

-XX:UserParNewGC

新生代使用並行回收,老年代使用序列回收。

新生代的的並行回收採用複製演算法,多執行緒回收(多核支援),可以使用XX:ParallelGCThreads限制執行緒的數量。當然,多執行緒並不一定快。

Parallel

類似於ParNew,新聲代採用複製演算法、老年代使用標記-壓縮演算法,更加關注吞吐量。

-XX:+UseParallelGC 使用Parallel收集器和老年代序列。

-XX:+UserParalleOldGC 使用Paralle收集器老年代並行。

-XX:MaxGCPauseMills 最大停頓時間,單位毫秒,GC盡力保證收回時間不超過設定的值,但不是肯定。

-XX:GCTimeRatio 在0-100中取值,代表垃圾收集時間佔總時間的比,預設99,代表最大允許1%的時間做GC。

“阿里架構師”的JVM之GC詳解

CMS收集器

Concurrent Mark Sweep 併發(與使用者執行緒一起執行)標記清除,這是一個老年代的收集器(新聲代使用的是ParNew),GC中應用執行緒停頓時間比較短,所以併發階段會降低吞吐量。

-XX:+UseConcMarkSweepGC

CMS收集器GC過程

  1. 初始標記

    根可以直接關聯到的物件,速度快

  2. 併發標記(和使用者執行緒一起)

    主要標記過程,標記全部物件

  3. 重新標記

    由於併發標記時,使用者執行緒依然執行,因此在正式清理前,再做修正

  4. 併發清除(和使用者執行緒一起)

    基於標記結果,直接清理物件

“阿里架構師”的JVM之GC詳解

CMS收集器特點

  1. 儘可能降低停頓

  2. 會影響系統整體吞吐量和效能。比如,在使用者執行緒執行過程中,分一半CPU去做GC,系統效能在GC階段,反應速度就下降一半。

  3. 清理不徹底。因為在清理階段,使用者執行緒還在執行,會產生新的垃圾,無法清理。

  4. 不能在空間快滿時再清理,因為和使用者執行緒一起執行。-XX:CMSInitiatingOccupancyFraction設定觸發GC的閾值,如果不幸記憶體預留空間不夠,就會引起concurrent mode failure。

CMS收集器常用引數

-XX:+UseCMSCompactAtFullCollection Full GC,進行一次整理,整理的過程是獨佔的,停頓時間較長。

-XX:+CMSFullGCsBeforeCompaction 設定幾次 Full GC後進行一次一次碎片整理。

-XX:ParallelCMSThreads 設定CMS的執行緒數量,一般約等於可用CPU的數量,不宜設定太大。

GC的引數整理

-XX:+UseSerialGC:在新生代和老年代使用序列收集器

-XX:SurvivorRatio:設定eden區大小和survivior區大小的比例

-XX:NewRatio:新生代和老年代的比

-XX:+UseParNewGC:在新生代使用並行收集器

-XX:+UseParallelGC :在新生代使用並行回收收集器

-XX:+UseParallelOldGC:老年代使用並行回收收集器

-XX:ParallelGCThreads:設定用於垃圾回收的執行緒數

-XX:+UseConcMarkSweepGC:新生代使用並行收集器,老年代使用CMS+序列收集器

-XX:ParallelCMSThreads:設定CMS的執行緒數量

-XX:CMSInitiatingOccupancyFraction:設定CMS收集器在老年代空間被使用多少後觸發

-XX:+UseCMSCompactAtFullCollection:設定CMS收集器在完成垃圾收集後是否要進行一次記憶體碎片的整理

-XX:CMSFullGCsBeforeCompaction:設定進行多少次CMS垃圾回收後,進行一次記憶體壓縮

-XX:+CMSClassUnloadingEnabled:允許對類後設資料進行回收

-XX:CMSInitiatingPermOccupancyFraction:當永久區佔用率達到這一百分比時,啟動CMS回收

-XX:UseCMSInitiatingOccupancyOnly:表示只在到達閥值的時候,才進行CMS回收

在此我向大家推薦一個架構學習交流群。交流學習群號: 744642380, 裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化、分散式架構等這些成為架構師必備的知識體系。還能領取免費的學習資源,目前受益良


相關文章