不可錯過的CMS學習筆記

引子

帶著問題去學習一個東西,才會有目標感,我先把一直以來自己對CMS的一些疑惑羅列了下,希望這篇學習筆記能解決掉這些疑惑,希望也能對你有所幫助。

  1. CMS出現的初衷、背景和目的?

  2. CMS的適用場景?

  3. CMS的trade-off是什么?優勢、劣勢和代價

  4. CMS會回收哪個區域的對象?

  5. CMS的GC Roots包括那些對象?

  6. CMS的過程?

  7. CMS和Full gc是不是一回事?

  8. CMS何時觸發?

  9. CMS的日志如何分析?

  10. CMS的調優如何做?

  11. CMS掃描那些對象?

  12. CMS和CMS collector的區別?

  13. CMS的推薦參數設置?

  14. 為什么ParNew可以和CMS配合使用,而Parallel Scanvenge不可以?

一、基礎知識

  1. CMS收集器:Mostly-Concurrent收集器,也稱并發標記清除收集器(Concurrent Mark-Sweep GC,CMS收集器),它管理新生代的方式與Parallel收集器和Serial收集器相同,而在老年代則是盡可能得并發執行,每個垃圾收集器周期只有2次短停頓。

  2. 我之前對CMS的理解,以為它是針對老年代的收集器。今天查閱了《Java性能優化權威指南》和《Java性能權威指南》兩本書,確認之前的理解是錯誤的。

  3. CMS的初衷和目的:為了消除Throught收集器和Serial收集器在Full GC周期中的長時間停頓。

  4. CMS的適用場景:如果你的應用需要更快的響應,不希望有長時間的停頓,同時你的CPU資源也比較豐富,就適合適用CMS收集器。

二、CMS的過程

CMS的正常過程

這里我們首先看下CMS并發收集周期正常完成的幾個狀態。

1.(STW)初始標記:這個階段是標記從GcRoots直接可達的老年代對象、新生代引用的老年代對象,就是下圖中灰色的點。這個過程是單線程的。

6e89fc0c6c0d1c4e222cfed13c9428b89eb778c3


2. 并發標記:由上一個階段標記過的對象,開始tracing過程,標記所有可達的對象,這個階段垃圾回收線程和應用線程同時運行,如上圖中的黃色的點。在并發標記過程中,應用線程還在跑,因此會導致有些對象會從新生代晉升到老年代、有些老年代的對象引用會被改變、有些對象會直接分配到老年代,這些受到影響的老年代對象所在的card會被標記為dirty,用于重新標記階段掃描。這個階段過程中,老年代對象的card被標記為dirty的可能原因,就是下圖中綠色的線:

0da5d6974f96ed4447edf697665fc19bf83b49a0


3. 預清理:預清理,也是用于標記老年代存活的對象,目的是為了讓重新標記階段的STW盡可能短。這個階段的目標是在并發標記階段被應用線程影響到的老年代對象,包括:(1)老年代中card為dirty的對象;(2)幸存區(from和to)中引用的老年代對象。因此,這個階段也需要掃描新生代+老年代。【PS:會不會掃描Eden區的對象,我看源代碼猜測是沒有,還需要繼續求證】

a5ec2ccbcbe12057e166dfa57dd8aef5da3f1ef1


4. 可中斷的預清理:這個階段的目標跟“預清理”階段相同,也是為了減輕重新標記階段的工作量。可中斷預清理的價值:在進入重新標記階段之前盡量等到一個Minor GC,盡量縮短重新標記階段的停頓時間。另外可中斷預清理會在Eden達到50%的時候開始,這時候離下一次minor gc還有半程的時間,這個還有另一個意義,即避免短時間內連著的兩個停頓,如下圖資料所示:

c55ad004b2ada7597e8dce57cc9f6a32a63d31c0
在預清理步驟后,如果滿足下面兩個條件,就不會開啟可中斷的預清理,直接進入重新標記階段:

如果不滿足上面兩個條件,則進入可中斷的預清理,可中斷預清理可能會執行多次,那么退出這個階段的出口有兩個(源碼參見下圖):


  • 設置了CMSMaxAbortablePrecleanLoops,并且執行的次數超過了這個值,這個參數的默認值是0;

  • CMSMaxAbortablePrecleanTime,執行可中斷預清理的時間超過了這個值,這個參數的默認值是5000毫秒。

    3e54929185056cbfeddb809cfed58f195b66862e
    如果是因為這個原因退出,gc日志打印如下:
    4aac0dc5a8b85e9cdb2931c96eb12d22a6b707c1

    有可能可中斷預清理過程中一直沒等到Minor gc,這時候進入重新標記階段的話,新生代還有很多活著的對象,就回導致STW變長,因此CMS還提供了CMSScavengeBeforeRemark參數,可以在進入重新標記之前強制進行依次Minor gc。

  • Eden的使用空間大于“CMSScheduleRemarkEdenSizeThreshold”,這個參數的默認值是2M;

  • Eden的使用率大于等于“CMSScheduleRemarkEdenPenetration”,這個參數的默認值是50%。


5.(STW)重新標記:重新掃描堆中的對象,進行可達性分析,標記活著的對象。這個階段掃描的目標是:新生代的對象 + Gc Roots + 前面被標記為dirty的card對應的老年代對象。如果預清理的工作沒做好,這一步掃描新生代的時候就會花很多時間,導致這個階段的停頓時間過長。這個過程是多線程的。

6. 并發清除:用戶線程被重新激活,同時將那些未被標記為存活的對象標記為不可達;

7. 并發重置:CMS內部重置回收器狀態,準備進入下一個并發回收周期。

CMS的異常情況

上面描述的是CMS的并發周期正常完成的情況,但是還有幾種CMS并發周期失敗的情況:

  1. 并發模式失敗(Concurrent mode failure):CMS的目標就是在回收老年代對象的時候不要停止全部應用線程,在并發周期執行期間,用戶的線程依然在運行,如果這時候如果應用線程向老年代請求分配的空間超過預留的空間(擔保失敗),就回觸發concurrent mode failure,然后CMS的并發周期就會被一次Full GC代替——停止全部應用進行垃圾收集,并進行空間壓縮。如果我們設置了UseCMSInitiatingOccupancyOnly和CMSInitiatingOccupancyFraction參數,其中CMSInitiatingOccupancyFraction的值是70,那預留空間就是老年代的30%。

  2. 晉升失敗:新生代做minor gc的時候,需要CMS的擔保機制確認老年代是否有足夠的空間容納要晉升的對象,擔保機制發現不夠,則報concurrent mode failure,如果擔保機制判斷是夠的,但是實際上由于碎片問題導致無法分配,就會報晉升失敗。

  3. 永久代空間(或Java8的元空間)耗盡,默認情況下,CMS不會對永久代進行收集,一旦永久代空間耗盡,就回觸發Full GC。

三、CMS的調優

1. 針對停頓時間過長的調優 首先需要判斷是哪個階段的停頓導致的,然后再針對具體的原因進行調優。使用CMS收集器的JVM可能引發停頓的情況有:(1)Minor gc的停頓;(2)并發周期里初始標記的停頓;(3)并發周期里重新標記的停頓;(4)Serial-Old收集老年代的停頓;(5)Full GC的停頓。其中并發模式失敗會導致第(4)種情況,晉升失敗和永久代空間耗盡會導致第(5)種情況。

2. 針對并發模式失敗的調優

  • 想辦法增大老年代的空間,增加整個堆的大小,或者減少年輕代的大小

  • 以更高的頻率執行后臺的回收線程,即提高CMS并發周期發生的頻率。設置UseCMSInitiatingOccupancyOnly和CMSInitiatingOccupancyFraction參數,調低CMSInitiatingOccupancyFraction的值,但是也不能調得太低,太低了會導致過多的無效的并發周期,會導致消耗CPU時間和更多的無效的停頓。通常來講,這個過程需要幾個迭代,但是還是有一定的套路,參見《Java性能權威指南》中給出的建議,摘抄如下:

  • 對特定的應用程序,該標志的更優值可以根據 GC 日志中 CMS 周期首次啟動失敗時的值得到。具體方法是,在垃圾回收日志中尋找并發模式失效,找到后再反向查找 CMS 周期最近的啟動記錄,然后根據日志來計算這時候的老年代空間占用值,然后設置一個比該值更小的值。

    增多回收線程的個數 CMS默認的垃圾收集線程數是(CPU個數 + 3)/4,這個公式的含義是:當CPU個數大于4個的時候,垃圾回收后臺線程至少占用25%的CPU資源。舉個例子:如果CPU核數是1~4個,那么會有1個CPU用于垃圾收集,如果CPU核數是5~8個,那么久會有2個CPU用于垃圾收集。

  • 針對永久代的調優 如果永久代需要垃圾回收(或元空間擴容),就會觸發Full GC。默認情況下,CMS不會處理永久代中的垃圾,可以通過開啟CMSPermGenSweepingEnabled配置來開啟永久代中的垃圾回收,開啟后會有一組后臺線程針對永久代做收集,需要注意的是,觸發永久代進行垃圾收集的指標跟觸發老年代進行垃圾收集的指標是獨立的,老年代的閾值可以通過CMSInitiatingPermOccupancyFraction參數設置,這個參數的默認值是80%。開啟對永久代的垃圾收集只是其中的一步,還需要開啟另一個參數——CMSClassUnloadingEnabled,使得在垃圾收集的時候可以卸載不用的類。


四、CMS的trade-off是什么?

  1. 優勢

  • 低延遲的收集器:幾乎沒有長時間的停頓,應用程序只在Minor gc以及后臺線程掃描老年代的時候發生極其短暫的停頓。

劣勢

  • 更高的CPU使用:必須有足夠的CPU資源用于運行后臺的垃圾收集線程,在應用程序線程運行的同時掃描堆的使用情況。【PS:現在服務器的CPU資源基本不是問題,這個點可以忽略】

  • CMS收集器對老年代收集的時候,不再進行任何壓縮和整理的工作,意味著老年代隨著應用的運行會變得碎片化;碎片過多會影響大對象的分配,雖然老年代還有很大的剩余空間,但是沒有連續的空間來分配大對象,這時候就會觸發Full GC。CMS提供了兩個參數來解決這個問題:(1)UseCMSCompactAtFullCollection,在要進行Full GC的時候進行內存碎片整理;(2)CMSFullGCsBeforeCompaction,每隔多少次不壓縮的Full GC后,執行一次帶壓縮的Full GC。

  • 會出現浮動垃圾;在并發清理階段,用戶線程仍然在運行,必須預留出空間給用戶線程使用,因此CMS比其他回收器需要更大的堆空間。

五、幾個問題的解答

  1. 為什么ParNew可以和CMS配合使用,而Parallel Scanvenge不可以? 答:這個跟Hotspot VM的歷史有關,Parallel Scanvenge是不在“分代框架”下開發的,而ParNew、CMS都是在分代框架下開發的。



  2. CMS中minor gc和major gc是順序發生的嗎? 答:不是的,可以交叉發生,即在并發周期執行過程中,是可以發生Minor gc的,這個找個gc日志就可以觀察到。



  3. CMS的并發收集周期合適觸發? 由下圖可以看出,CMS 并發周期觸發的條件有兩個:

    6cf94df0756378f83e794122ee6b19c94c57b7b1


  • 閾值檢查機制:老年代的使用空間達到某個閾值,JVM的默認值是92%(jdk1.5之前是68%,jdk1.6之后是92%),或者可以通過CMSInitiatingOccupancyFraction和UseCMSInitiatingOccupancyOnly兩個參數來設置;這個參數的設置需要看應用場景,設置得太小,會導致CMS頻繁發生,設置得太大,會導致過多的并發模式失敗。例如

  • 動態檢查機制:JVM會根據最近的回收歷史,估算下一次老年代被耗盡的時間,快到這個時間的時候就啟動一個并發周期。設置UseCMSInitiatingOccupancyOnly這個參數可以將這個特性關閉。


CMS的并發收集周期會掃描哪些對象?會回收哪些對象? 答:CMS的并發周期只會回收老年代的對象,但是在標記老年代的存活對象時,可能有些對象會被年輕代的對象引用,因此需要掃描整個堆的對象。



CMS的gc roots包括哪些對象? 答:首先,在JVM垃圾收集中Gc Roots的概念如何理解(參見R大對GC roots的概念的解釋);第二,CMS的并發收集周期中,如何判斷老年代的對象是活著?我們前面提到了,在CMS的并發周期中,僅僅掃描Gc Roots直達的對象會有遺漏,還需要掃描新生代的對象。如下圖中的藍色字體所示,CMS中的年輕代和老年代是分別收集的,因此在判斷年輕代的對象存活的時候,需要把老年代當作自己的GcRoots,這時候并不需要掃描老年代的全部對象,而是使用了card table數據結構,如果一個老年代對象引用了年輕代的對象,則card中的值會被設置為特殊的數值;反過來判斷老年代對象存活的時候,也需要把年輕代當作自己的Gc Roots,這個過程我們在第三節已經論述過了。

f250905f2f0f3a159f43b0007340be8019387168


如果我的應用決定使用CMS收集器,推薦的JVM參數是什么?我自己的應用使用的參數如下,是根據PerfMa的xxfox生成的,大家也可以使用這個產品調優自己的JVM參數:
  1. -Xmx4096M-Xms4096M-Xmn1536M

  2. -XX:MaxMetaspaceSize=512M-XX:MetaspaceSize=512M

  3. -XX:+UseConcMarkSweepGC

  4. -XX:+UseCMSInitiatingOccupancyOnly

  5. -XX:CMSInitiatingOccupancyFraction=70

  6. -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

  7. -XX:+CMSClassUnloadingEnabled

  8. -XX:+ParallelRefProcEnabled

  9. -XX:+CMSScavengeBeforeRemark

  10. -XX:ErrorFile=/home/admin/logs/xelephant/hs_err_pid%p.log

  11. -Xloggc:/home/admin/logs/xelephant/gc.log

  12. -XX:HeapDumpPath=/home/admin/logs/xelephant

  13. -XX:+PrintGCDetails

  14. -XX:+PrintGCDateStamps

  15. -XX:+HeapDumpOnOutOfMemoryError


CMS相關的參數總結(需要注意的是,這里我沒有考慮太多JDK版本的問題,JDK1.7和JDK1.8這些參數的配置,有些默認值可能不一樣,具體使用的時候還需要根據具體的版本來確認怎么設置)

bf70a4b790d34f5f7f3b223d42f8fef318e13ae5

參考資料

  1. 從實際案例聊聊Java應用的GC優化

  2. 理解CMS垃圾回收日志

  3. 圖解CMS垃圾回收機制,你值得擁有

  4. 為什么CMS雖然是老年代的gc,但仍要掃描新生代的?

  5. R大對GC roots的概念的解釋

  6. Introduce to CMS Collector

  7. 《深入理解Java虛擬機》

  8. 《Java性能權威指南》

  9. Oracle的GC調優手冊

  10. what-is-the-threshold-for-cms-old-gc-to-be-triggered

  11. Frequently Asked Questions about Garbage Collection in the Hotspot Java VirtualMachine

  12. Java SE HotSpot at a Glance

  13. xxfox:PerfMa的參數調優神器

  14. 詳解CMS垃圾回收機制

  15. ParNew和PSYoungGen和DefNew是一個東西么?

  16. Java SE的內存管理白皮書

原文發布時間為: 2018-12-03
本文作者:阿杜的世界
本文來自云棲社區合作伙伴“ Kirito的技術分享”,了解相關信息可以關注“Kirito的技術分享”。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/252891.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/252891.shtml
英文地址,請注明出處:http://en.pswp.cn/news/252891.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

團隊合作及個人成長

通過前一章的學習,我了解到了關于建模的很多初步的知識,但是這和成為一名軟件工程師是遠遠不夠的,完成一個程序通常都是一個團隊,而這個團隊重要的肯定是需要一些流程,這樣才能讓程序有條不紊的運行著。在這么一個團隊…

相機工作原理

輕輕一按,你的相機就把光子轉換為了比特。于是一張相片就保存到了你的 iPhone 里。 讓我們假設一下你身處室外,環顧四周。三億里之外,太陽無時無刻不在發射光子。它們需要花上 8 分鐘之久才能到達我們舒適的星球。有一些光子撞擊到你周圍的物…

Android Studio使用心得

說實話 開始接觸這個工具 真的認為非常惡心 畢竟大陸被墻 非常多東西用起來不是非常方便 并且Eclipse轉到Android Studio還是一個跨度 廢話不多說 以下 講下我遇到的問題 1. 安裝的時候(Setup Wizard - Download Components) 這個要下載非常長時間 甚至…

CentOS用戶和用戶組的操作

2019獨角獸企業重金招聘Python工程師標準>>> CentOS用戶和用戶組的操作 長得太帥忚四種檌 關注 2018.05.12 16:40* 字數 312 閱讀 115評論 0喜歡 0 用戶組的操作 1.添加用戶組: groupadd 組名2.修改組名 groupmod -n 新組名 原組名刪除用戶組groupdel 組…

vue webpack配置分析

寫在前面:作為 Vue 的使用者我們對于 vue-cli 都很熟悉,但是對它的 webpack 配置我們可能關注甚少,今天我們為大家帶來 vue-cli#2.0 的 webpack 配置分析 vue-cli 的簡介、安裝我們不在這里贅述,對它還不熟悉的同學可以直接訪問 v…

Linux用戶空間與內核地址空間

Linux 操作系統和驅動程序運行在內核空間,應用程序運行在用戶空間,兩者不能簡單地使用指針傳遞數據,因為Linux使用的虛擬內存機制,用戶空間的數據可能被換出,當內核空間使用用戶空間指針時,對應的數據可能不…

關于主鍵的設計、primary key

主鍵:用于唯一標識一個表中一行數據。 外鍵:用于建立兩個表之間的關系,A表中有一列是B表中的主鍵,那么A表中這列的數據就受到B表主鍵的約束。 那么關于主鍵應該如何設計呢,這里我說下優缺點: 1.用自動增長字…

淺談微信smali注入

作者:郭少雷 搞android搞了幾年也沒搞出個啥牛逼app出來,眼看時下最火的app微信如此火熱,實在想搞搞它,索性就想著給它加點東西進去。 以下內容純屬本人個人愛好,僅限個人學習android用途以及對android的深入了解。 首…

從0到1使用Kubernetes系列(四):搭建第一個應用程序

上一篇文章《從0到1使用Kubernetes系列(三):使用Ansible安裝Kubernetes集群》中,我們搭建了一套Kubernetes集群,接下來將在本文中介紹如何使用Kubernetes部署一個Nginx并通過Pod IP、Service IP、Ingress這三種方式訪問…

Embeded linux之移植iptables

一、內核環境&#xff1a; linux-3.4.35 -*- Networking support ---> Networking options ---> [*] Network packet filtering framework (Netfilter) ---> IP: Netfilter Configuration ---> <*> IP tables support (required for filtering/masq/NAT)…

Hadoop HIVE

數據倉庫工具。構建在hadoop上的數據倉庫框架&#xff0c;可以把hadoop下的原始結構化數據變成Hive中的表。&#xff08;主要解決ad-hoc query&#xff0c;即時查詢的問題&#xff09; 支持一種與SQL幾乎完全相同的語言HQL。除了不支持更新&#xff0c;索引和事務&#xff0c;幾…

Xcode9學習筆記67 - 打印查看程序沙箱結構中常用的幾個目錄

override func viewDidLoad() {super.viewDidLoad()// Do any additional setup after loading the view, typically from a nib.//首先獲得應用程序目錄的路徑&#xff0c;在該目錄下有三個文件夾&#xff1a;文檔目錄、庫目錄、臨時目錄以及一個程序包。該目錄就是應用程序的…

檢測raid類型和磁盤壞道腳本

#!/bin/sh #腳本功能&#xff1a; #安裝工具MegaCli64 #Host Information&#xff1a;主機名和ip地址 #Raid Information&#xff1a;raid信息和充電狀態 #WARNING Information&#xff1a;MediaErrcount檢測壞塊和哪塊盤 #Disk Information&#xff1a;磁盤信息 #上傳MegaC…

簡單論述市場營銷管理的基本過程

http://www.chinadmd.com/file/uvc3uaosocwevsetrzpaereo_1.html

Javascript-Switch

JavaScript Switch 語句 請使用 switch 語句來選擇要執行的多個代碼塊之一。 語法 switch(n) { case 1:執行代碼塊 1break; case 2:執行代碼塊 2break; default:n 與 case 1 和 case 2 不同時執行的代碼 }工作原理&#xff1a;首先設置表達式 n&#xff08;通常是一個變量&…

《SpringBoot揭秘 快速構建微服務體系》讀后感(三)

SpringApplication&#xff1a;SpringBoot程序啟動的一站式解決方案 深入探索SpringApplication執行流程 因為書上的版本是1.2的&#xff0c;比較老&#xff0c;這里參考http://blog.csdn.net/zxzzxzzxz123/article/details/69941910 public ConfigurableApplicationContext ru…

裝飾器函數

1.裝飾器 ? 裝飾器&#xff1a;在不改變原函數的調用方式和函數&#xff0c;額外的增加功能 簡單裝飾器def timer(func):def inner():print(time.time())func() # 原來的函數return inner ? timer # func1 timer(func1) def func1():print(func1) 函數帶返回值def timer…

6G SDI/12G SDI 基帶信號無壓縮傳輸方案介紹

認知數字像素分辨率&#xff1a; 首先從分辨率(數字像素)角度來講&#xff0c;從標清時代走到高清&#xff0c;從720x576到現在的1920x1080&#xff0c;寬高比從4:3到16:9&#xff0c;這個是我們比較熟悉的&#xff0c;4K實際上是建立在高清基礎之上的&#xff0c;我們稱之為“…

4月18日

u盤丟了&#xff0c;毀滅性的災難 希望明天可以找到它&#xff0c;不然萬字的策劃案就要重寫 這是一個災難 轉載于:https://www.cnblogs.com/dandansang/p/6731174.html

玩轉SSH端口轉發

ssh端口轉發(tunnel) 我們在實施項目部署時經常會遇到一種問題&#xff0c;那就是當我們給一些安全系數高的客戶部署服務時&#xff0c;大多都不會給我們提供公網訪問的權限&#xff0c;但是很多時候為了方便又會允許服務器直接訪問公網&#xff0c;遇到這種情況大多有兩種辦法…