關於JVM的垃圾回收(GC) 這可能是你想了解的


垃圾收集 (Garbage Collection) 機制是Java語言的一大優勢特性, 為充分榨取JVM性能, 避免系統因垃圾收集不及時導致的OOM (OutOfMemory, 內存溢出)問題, 或內存飽和出現無法響應用戶請求的情況, 就需要根據服務器配置及應用復雜度對GC策略進行優化, 以確保系統正常運行.

1 JVM中Java對象的分類

JVM根據運行於其中的對象的生存時間, 將它們分為3種, 並分別存放在JVM的不同內存區域中. 這種對象存放空間的管理方式叫做Generation管理方式.

(1) Young Generation (新生代, 又稱年輕代): 用於存放"早逝"對象(即瞬時對象), 一般的Java應用中, 80%的對象都是"朝生息滅"的, 比如在創建對象或調用方法時使用的臨時對象或局部變量.

(2) Tenured Generation (老年代): 用於存放"駐留"對象(即被引用較長時間的對象). 往往體現為一個大型程序中的全局對象或長時間被使用的對象.

(3) Perm Generation (永久代): 用於存放"永久"對象. 這些對象管理着運行於JVM中的類和方法.

2 JVM的GC類型及觸發條件

2.1 Young GC

又叫Minor GC(次收集), Young GC經常發生, 且其每次消耗的時間較短.

—— 它只對Young Generation中的對象進行垃圾收集.

觸發條件:

在Young Generation(新生代)的Eden區的空間不足以容納新生成的對象時執行, 同時會將 Eden 區與 From Survivor 區中尚且存活的對象移動至空閑的 To Survivor 區中.

—— 程序運行過程中, 始終有一個 Survivor 區是完全處於空閑狀態的, 如果不是, 說明應用程序出現故障了.

2.2 Full GC

又叫Major GC(主收集), 是對整個Java Heap中的對象(不包括永久代/元空間)進行垃圾收集.

Full GC操作耗時久, 對系統的性能影響較大, 因此在 JVM 的調優中, 很多工作是針對 Full GC 的調優 —— 要盡可能減少Full GC的頻率.

Full GC 是一種"昂貴"的垃圾收集方式, 它要對整個Heap進行垃圾收集, 並做一定的空間整理, 這會使Stop-The-World的時間變長.

Full GC的觸發條件:

(1) 年老代(Tenured)空間不足:

  • 通過Minor GC后進入老年代的對象的體積大於老年代的可用空間;
  • 由Eden塊、From Space塊向To Space復制存活對象時, 它們的體積大於To Space的大小, 系統就會把這些對象轉存到老年代, 而老年代的可用空間小於這些對象的體積.

(2) System.gc()方法被顯式調用, 系統建議執行Full GC, 但並不會立即執行 —— 非常影響程序性能, 建議禁止使用;

(3) 上一次GC之后Heap中各個區域空間的動態變化.

3 Java對象生成時的內存申請過程

(1) JVM會試圖為相關Java對象在年輕代的Eden區中初始化一塊內存區域;

(2) 當Eden區空間足夠時, 內存申請結束. 否則執行下一步;

(3) JVM 試圖釋放在Eden區中所有不活躍的對象(即 出發Young GC), 釋放后若Eden空間仍然不足以放入新對象時, JVM會試圖將部分Eden區中活躍的對象遷移至Survivor區;

(4) Survivor區被用來作為Eden區及老年代的中間交換區域, 當老年代空間足夠時, Survivor區中存活了一定次數的對象會被遷移到老年代;

(5) 當年老代空間不夠時, JVM會在老年代進行完全的垃圾回收(Full GC);

(6) Full GC后, 若Survivor區及老年代仍然無法存放從Eden區復制過來的對象, 則會導致JVM無法在 Eden區為新生成的對象申請內存, 即出現 "Out of Memory".

OOM(Out of Memory)異常一般主要有如下2種原因:

(1) 老年代溢出, 表現為: java.lang.OutOfMemoryError:Javaheapspace, 這是最常見的情況, 產生的原因可能是: 設置的內存參數-Xmx過小或程序的內存泄露及使用不當問題.

(2) 持久代溢出,表現為: java.lang.OutOfMemoryError:PermGenspace, 通常由於持久代設置過小, 動態加載了大量 Java 類而導致溢出, 解決辦法唯有將參數 -XX:MaxPermSize 調大(一般256MB能滿足絕大多數應用程序需求).

3 Oracle JDK中的垃圾收集器

3.1 串行收集器(Serial Collector)

只有一條GC線程, 運行時需要暫停用戶程序(Stop-The-World).

實現: Serial(用於新生代, 采用復制算法)、serial old(用於老年代, 采用標記-整理算法).

3.2 並行收集器(Parallel Collector)

有多條GC線程, 運行時也需要暫停用戶程序(Stop-The-World).

實現: ParNew(用於新生代, 采用復制算法)、Parallel Scavenge(用於新生代, 采用復制算法)、Parallel Old(用於老年代, 采用標記-整理算法).

3.3 並發收集器(Concurrent Collector)

有一條或多條GC線程, 且需要在部分階段暫停用戶程序(Stop-The-World), 部分階段與用戶程序並發執行.

實現: Concurrent Mark Sweep(CMS, 用於老年代, 采用標記-清除算法).

並發(concurrent)與並行(parallel)的比較:

(1) 並發就是兩個任務(A和B)需要獨立運行, 在任務A結束之前, 任務B開始執行 —— 即表面上多個任務同時執行.
(2) 並行, 類比串行, 是微觀概念, 即在每一個時刻都有多個任務在同時執行, 形象點理解為多管齊下, 串行可理解為單列隊列, 同一時刻只能執行一個任務.
(3) 事實上, 並行是並發的一種實現方式, 還有一種並發的實現方式, 即我們熟悉的時間片切換 —— 任務A執行一段時間, CPU再切換到任務B執行一段時間, 如此交替執行. 時間片切換在微觀上仍然是串行 —— 某一具體時刻只有一個任務在執行, 而在宏觀上, 即一段時間內, 有多個任務得到了執行.
(4) 總結: 並行必須在多核多處理器或分布式系統(本質還是多核多處理器)中才能發生, 而單核處理器上只能發生時間片切換.

3.4 G1收集器(Garbage First GC)

G1垃圾回收器在 Oracle JDK 7 開始提供完整支持, 它是 server 型的 GC, 主要針對多核處理器和大內存的服務器, 能夠以很高的概率 滿足開發人員對停頓時間的要求, 同時還能保證高吞吐量.

(1) 與CMS收集器相比, G1收集器的優勢:

① 基於標記-整理算法, 不會產生大量的內存碎片;
② 可以更加精確地控制停頓時間, 在不犧牲吞吐量前提下, 實現低停頓垃圾回收.

(2) G1收集器的實現原理:

G1收集器能夠避免全區域的垃圾收集, 它把堆內存划分為大小固定的幾個獨立區域, 並跟蹤這些區域的垃圾收集進度, 同時在后台維護一個優先級列表, 每次根據所允許的收集時間, 優先回收垃圾最多的區域.
—— **區域划分和優先級區域回收機制, 確保G1收集器可以在有限的時間內獲得最高的垃圾收集效率. **

G1的長期目標是取代CMS (Concurrent Mark-Sweep Collector) 並發標記-清除收集器.

3.5 其他概念說明

(1) 為了更大程度地榨取機器性能, 新生代的收集器都使用了復制算法, 老年代的收集器都使用 標記-清除 或 標記-整理 算法. 關於各算法詳情, 請參閱: JVM內存管理———垃圾搜集器參數精解.

(2) 在實際應用中, 需要對JVM的新生代、老年代分別選擇合適的垃圾收集器.

(3) 這里新生代和老年代都分別有三種實現, 但由於收集器的實現方式不同, 部分組合無法一起配合工作, 經過驗證, 這六種垃圾收集器只有六種可用組合.

4 GC的配置參數

4.1 參數名稱的說明

4.1.1 標准參數(-)

所有 JVM 都必須支持這些參數的功能, 而且向后兼容, 如:

-client : 設置 JVM 使用 client 模式, 特點是啟動速度比較快, 但運行時性能和內存管理效率不高. 通常用於客戶端應用程序或開發調試; 在32位環境下直接運行 Java 程序默認啟用該模式.

-server : 設置 JVM 使 server 模式, 特點是啟動速度比較慢, 但運行時性能和內存管理效率很高, 適用於生產環境; 在具有64位能力的JDK環境下默認啟用該模式.

4.1.2 非標准參數(-X)

各 JVM 廠商應該全部實現這些參數的功能, 但並不能保證這些廠商提供的 JVM 中都實現了這些功能, 且不保證向后兼容;

4.1.3 非穩定參數(-XX)

此類參數在各個 JVM 的實現中會有所不同, 將來也可能不被支持, 要慎重使用.

注意: 在"-XX:"后的參數若不需要賦值, 即只是用來配置開啟或關閉相應選項, 則需要有 **"+" (開啟) 或 "-" (禁止) **, 否則應用程序將在日志文件 (如 Tomcat 的日志文件 catalina.out ) 中拋出如下錯誤:

Missing +/- setting for VM option 'UseConcMarkSweepGC'. 
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit. 

可以看出, 這里缺少了 "+/-" 符號, 導致虛擬機啟動異常, 修改后即可正常啟動.

4.2 串行GC參數

-XX:+UseSerialGC 
# 使用 Serial & Serial Old 串行收集器(JDK 5以前的主要收集方式), 是client模式的默認設置. 

4.3 並行GC參數(吞吐量優先)

-XX:+UseParNewGC 
# 使用ParNew & Serial Old收集器, 即對新生代使用並行收集, 提高收集效率(縮短Young GC的時間), 不推薦. 

-XX:+UseParallelGC 
# 使用Parallel Scavenge & Parallel Old並行收集器, 吞吐量優先, 會消耗更多內存, 是server模式的默認設置. 

-XX:+UseParallelOldGC 
# 對老年代使用Parallel Old並行收集器(JDK 6開始支持). 

-XX:ParallelGCThreads=20 
# 配置並行收集器的線程數, 即並行執行垃圾收集任務的線程個數. 
# 此值最好與CPU處理器的個數相同(默認即相同).

-XX:GCTimeRatio=49 
# 設置系統用作GC的時間比例, 如49, 則GC時間比為 1/(1+49) = 2%, 即Java用2%的時間來做垃圾收集. 
# 如果此值設置過大, 即GC時間太少導致GC無法完成, JVM會壓縮新生代的大小以適應此配置. 

-XX:MaxGCPauseMillis=100 
# 設置每次新生代垃圾收集的最長毫秒值, 如果時間久而新生代的大小不足以支撐到此時間, 
# JVM會自動調整新生代的大小以滿足此值. 若仍然無法滿足, 則會調整GCTimeRatio. 

-XX:+UseAdaptiveSizePolicy 
# 使並行收集器自動設定Eden區的大小與相應的Survivor區的比例, 
# 以達到目標系統規定的最低響應時間或收集頻率等. 建議在使用並行收集器時始終開啟此選項. 

4.4 並發GC參數(響應時間優先)

-XX:+UseConcMarkSweepGC 
# JDK 5開始提供支持, 以響應時間優先--即縮短Full GC的時間. 
# JVM會根據系統配置自行設置使用ParNewGC & CMS(Serial Old作為替補)收集器. 
# 建議在Heap Size較大且Full GC時間較長, 對響應時間的需求大於對吞吐量的需求, 能夠承受垃圾回收線程和應用線程共享CPU資源等情況下使用. 

-XX:+UseCMSCompactAtFullCollection  
# CMS是不會移動內存的, 此參數設置在每次Full GC后對老年代空間進行壓縮整理, 會影響性能, 但是能減少內存碎片. 

-XX:CMSInitiatingOccupancyFraction=70 
# 觸發CMS收集器的內存占用閾值, 默認為90%: 當老年代內存空間使用率達到90%時, 就開始對老年代進行CMS並發垃圾收集. 
# 這個參數設置不當, 將發生promotion failed(晉升失敗). 

-XX:CMSFullGCsBeforeCompaction=10 
# 由於並發收集器不對內存空間進行壓縮整理, 所以運行一段時間后會產生"碎片", 使得運行效率降低. 此配置項用來設置在幾次GC后觸發一次內存整理. 
# 此配置項即將被移除(JDK 8已不建議使用). 

4.5 G1 GC參數

-XX:+UseG1GC 
# 使用G1收集器

-XX:MaxGCPauseMillis=200	
# 設置回收器的最大停頓毫秒值, 這是一個概率目標, JVM將盡最大努力去實現它. 

-XX:G1ReservePercent=15
# 設置堆的臨時上限, 以防止因堆擴大失敗而導致的異常. 默認值是10. 

-XX:G1HeapRegionSize=16
# 使用 G1 的 Java 堆細分為大小相等的區域(Region), 此選項是設置單個區域的大小, 
# 默認值是基於堆大小的一種人體工效划分, 最小值是1Mb, 最大值是32Mb.
# 人體工效: 根據平台相關的默認選擇和根據需求動態垃圾回收的行為統稱為人體工效, 
# 人體工效的作用就是可以通過少量的命令行選項就可以讓JVM提供最合適的性能. 

4.6 通用GC參數

-Xnoclassgc 
# 禁用類垃圾收集, 能提高性能. 

-XX:MaxHeapFreeRatio=70	
# GC過后堆的最大空閑空間比例, 避免過渡壓縮

-XX:MinHeapFreeRatio=40	
# GC過后堆的最小空閑空間比例, 避免過度膨脹

-XX:MaxTenuringThreshold=5 
# 晉升老年代的最大年齡, 默認為15: 新生代對象在15次Minor GC后將被轉移至老年代. -- 必須在0-15之間. 
# 如果設置為0, 等同於去掉了新生代的空間, 新生代對象將會越過Survivor區直接進入老年代, 很快就會占滿老年代發生Full GC. 
# 同時, 這在老年代對象較多的應用中卻可以提高效率. 
# 如果將此值調大, 則新生代對象會在Survivor區進行多次復制, 即增加了對象在新生代的存活時間. 

-XX:PretenureSizeThreshold=10 
# 晉升老年代的對象的大小, 默認為0. 比如設為10M, 則超過10M的對象將越過Eden區, 直接存入老年代.

-XX:+UseThreadPriorities 
# 啟用本地線程優先級API, 使java.lang.Thread.setPriority()生效, 不啟用則無效

-XX:+DisableExplicitGC 
# 禁用寫在程序中的System.gc(), 即禁止開發人員調用gc()方法影響性能. 

-XX:+ExplicitGCInvokesConcurrent 
# 配置System.gc()可以和應用程序一起並發執行. 
# System.gc()用來回收不用的內存, 此方法只是"建議"JVM回收內存, 不能強制回收, 具體回收時機由JVM決定. 

-XX:TargetSurvivorRatio=90 
# 允許90%的Survivor區被占用(JVM默認為50%), 提高對於Survivor區的使用率

-XX:SoftRefLRUPolicyMSPerMB=1 
# Soft Reference(弱引用)在虛擬機中比在客戶機中存活的時間更長, 其清除頻率可用此命令來控制. 
# 此命令用來指定每MB堆空間中Soft Reference存活的秒數, 默認值為1000毫秒: 對象的最后一個強引用被收集之后, 存活1秒鍾. 
# 這是個近似值: Soft Reference只會在垃圾收集時才會被清除, 而垃圾收集並不總是發生. 可改為0, 客戶機中不使用就立即清除. 

4.7 其他說明

(1) 在內存調優中, 內存設置越大, 處理請求的效率也就越高, 但同時垃圾收集所需要的時間也就越長, 在垃圾收集期間的處理效率肯定會受影響, 因此需要作出相應的權衡.

(2) 關於 CMS 收集器的 Promotion FailedConcurrent Mode Failure :

  • Promotion Failed, 晉升失敗: 發生Young GC時, Eden區存活的對象 和 Eden區的From塊中存活的對象, 兩者的體積超過了Eden區中To塊的大小, Young GC的悲觀策略將使這些存活的對象都遷移到老年代, 而此時老年代的大小不足以容納這些對象, 從而發生promotion failed , 程序將暫停很長時間.

  • Concurrent Mode Failure, 並發修改失敗: 在執行GC的過程中, 恰好有對象要晉升至老年代, 而此時老年代的空間不足, 從而造成Concurrent Mode Failure.

  • 這兩種情況都可能觸發Full GC. 要注意不要一次性占用太多的內存, 或對JVM的配置進行一定的優化.

  • CMSInitiatingOccupancyFraction的設置技巧: 參考: CMSInitiatingOccupancyFraction計算公式

    (Xmx-Xmn) * (1 - CMSInitiatingOccupancyFraction/100) >= (Xmn - Xmn/(SurvivorRatior+2))

    進而推斷出:

    CMSInitiatingOccupancyFraction <= ( (Xmx-Xmn) - (Xmn - Xmn/(SurvivorRatior+2) ) ) / (Xmx-Xmn) * 100

參考資料

JVM系列三:JVM參數設置、分析

G1(Garbage-First)垃圾回收器

JVM內存管理——垃圾搜集器參數精解

版權聲明

作者: 馬瘦風

出處: 博客園 馬瘦風的博客

您的支持是對博主的極大鼓勵, 感謝您的閱讀.

本文版權歸博主所有, 歡迎轉載, 但請保留此段聲明, 並在文章頁面明顯位置給出原文鏈接, 否則博主保留追究相關人員法律責任的權利.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM