Java內存溢出優化性能優化

 

高性能應用構成了現代網絡的支柱。LinkedIn有許多內部高吞吐量服務來知足每秒數千次的用戶請求。要優化用戶體驗,低延遲地響應這些請求很是重要。html

好比說,用戶常常用到的一個功能是瞭解動態信息——不斷更新的專業活動和內容的列表。動態信息在LinkedIn隨處可見,包括公司頁面,學校頁面以及最重要的主頁。基礎動態信息數據平臺爲咱們的經濟圖譜(會員,公司,羣組等等)中各類實體的更新創建索引,它必須高吞吐低延遲地實現相關的更新。java


圖1 LinkedIn 動態信息linux

這些高吞吐低延遲的Java應用轉變爲產品,開發人員必須確保應用開發週期的每一個階段一致的性能。肯定優化垃圾回收(Garbage Collection,GC)的設置對達到這些指標很是關鍵。git

本文章經過一系列步驟來明確需求並優化GC,目標讀者是爲實現應用的高吞吐低延遲,對使用系統方法優化GC感興趣的開發人員。文章中的方法來自於LinkedIn構建下一代動態信息數據平臺過程。這些方法包括但不侷限於如下幾點:併發標記清除(Concurrent Mark Sweep,CMS)和G1垃圾回收器的CPU和內存開銷,避免長期存活對象引發的持續GC週期,優化GC線程任務分配使性能提高,以及GC停頓時間可預測所需的OS設置。github

優化GC的正確時機?

GC運行隨着代碼級的優化和工做負載而發生變化。所以在一個已實施性能優化的接近完成的代碼庫上調整GC很是重要。可是在端到端的基本原型上進行初步分析也頗有必要,該原型系統使用存根代碼並模擬了可表明產品環境的工做負載。這樣能夠捕捉該架構延遲和吞吐量的真實邊界,進而決定是否縱向或橫向擴展。web

在下一代動態信息數據平臺的原型階段,幾乎實現了全部端到端的功能,而且模擬了當前產品基礎架構所服務的查詢負載。從中咱們得到了多種用來衡量應用性能的工做負載特徵和足夠長時間運行狀況下的GC特徵。算法

優化GC的步驟

下面是爲知足高吞吐,低延遲需求優化GC的整體步驟。也包括在動態信息數據平臺原型實施的具體細節。能夠看到在ParNew/CMS有最好的性能,但咱們也實驗了G1垃圾回收器。shell

1.理解GC基礎知識

理解GC工做機制很是重要,由於須要調整大量的參數。Oracle的Hotspot JVM 內存管理白皮書是開始學習Hotspot JVM GC算法很是好的資料。瞭解G1垃圾回收器,請查看該論文緩存

2. 仔細考量GC需求

爲下降應用性能的GC開銷,能夠優化GC的一些特徵。吞吐量、延遲等這些GC特徵應該長時間測試運行觀察,確保特徵數據來自於應用程序的處理對象數量發生變化的多個GC週期。性能優化

  • Stop-the-world回收器回收垃圾時會暫停應用線程。停頓的時長和頻率不該該對應用遵照SLA產生不利的影響。
  • 併發GC算法與應用線程競爭CPU週期。這個開銷不該該影響應用吞吐量。
  • 不壓縮GC算法會引發堆碎片化,致使full GC長時間Stop-the-world停頓。
  • 垃圾回收工做須要佔用內存。一些GC算法產生更高的內存佔用。若是應用程序須要較大的堆空間,要確保GC的內存開銷不能太大。
  • 清晰地瞭解GC日誌和經常使用的JVM參數對簡單調整GC運行頗有必要。GC運行隨着代碼複雜度增加或者工做特性變化而改變。

咱們使用Linux OS的Hotspot Java7u51,32GB堆內存,6GB新生代(young generation)和-XX:CMSInitiatingOccupancyFraction值爲70(老年代GC觸發時其空間佔用率)開始實驗。設置較大的堆內存用來維持長期存活對象的對象緩存。一旦這個緩存被填充,提高到老年代的對象比例顯著降低。

使用初始的GC配置,每三秒發生一次80ms的新生代GC停頓,超過百分之99.9的應用延遲100ms。這樣的GC極可能適合於SLA不太嚴格要求延遲的許多應用。然而,咱們的目標是儘量下降百分之99.9應用的延遲,爲此GC優化是必不可少的。

3.理解GC指標

優化以前要先衡量。瞭解GC日誌的詳細細節(使用這些選項:-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime)能夠對該應用的GC特徵有整體的把握。

LinkedIn的內部監控和報表系統,inGraphsNaarad,生成了各類有用的指標可視化圖形,好比GC停頓時間百分比,一次停頓最大持續時間,長時間內GC頻率。除了Naarad,有不少開源工具好比gclogviewer能夠從GC日誌建立可視化圖形。

在這個階段,須要肯定GC頻率和停頓時長是否影響應用知足延遲性需求的能力。

4.下降GC頻率

在分代GC算法中,下降回收頻率能夠經過:(1)下降對象分配/提高率;(2)增長代空間的大小。

在Hotspot JVM中,新生代GC停頓時間取決於一次垃圾回收後對象的數量,而不是新生代自身的大小。增長新生代大小對於應用性能的影響須要仔細評估:

  • 若是更多的數據存活並且被複制到survivor區域,或者每次垃圾回收更多的數據提高到老年代,增長新生代大小可能致使更長的新生代GC停頓。
  • 另外一方面,若是每次垃圾回收後存活對象數量不會大幅增長,停頓時間可能不會延長。在這種狀況下,減小GC頻率可能使應用整體延遲下降和(或)吞吐量增長。

對於大部分爲短時間存活對象的應用,僅僅須要控制前面所說的參數。對於建立長期存活對象的應用,就須要注意,被提高的對象可能很長時間都不能被老年代GC週期回收。若是老年代GC觸發閾值(老年代空間佔用率百分比)比較低,應用將陷入不斷的GC週期。設置高的GC觸發閾值可避免這一問題。

因爲咱們的應用在堆中維持了長期存活對象的較大緩存,將老年代GC觸發閾值設置爲-XX:CMSInitiatingOccupancyFraction=92 -XX:+UseCMSInitiatingOccupancyOnly。咱們也試圖增長新生代大小來減小新生代回收頻率,可是並無採用,由於這增長了應用延遲。

5.縮短GC停頓時間

減小新生代大小能夠縮短新生代GC停頓時間,由於這樣被複制到survivor區域或者被提高的數據更少。可是,正如前面提到的,咱們要觀察減小新生代大小和由此致使的GC頻率增長對於總體應用吞吐量和延遲的影響。新生代GC停頓時間也依賴於tenuring threshold(提高閾值)和空間大小(見第6步)。

使用CMS嘗試最小化堆碎片和與之關聯的老年代垃圾回收full GC停頓時間。經過控制對象提高比例和減少-XX:CMSInitiatingOccupancyFraction的值使老年代GC在低閾值時觸發。全部選項的細節調整和他們相關的權衡,請查看Web Services的Java 垃圾回收Java 垃圾回收精粹

咱們觀察到Eden區域的大部分新生代被回收,幾乎沒有對象在survivor區域死亡,因此咱們將tenuring threshold從8下降到2(使用選項:-XX:MaxTenuringThreshold=2),爲的是縮短新生代垃圾回收消耗在數據複製上的時間。

咱們也注意到新生代回收停頓時間隨着老年代空間佔用率上升而延長。這意味着來自老年代的壓力使得對象提高花費更多的時間。爲解決這個問題,將總的堆內存大小增長到40GB,減少-XX:CMSInitiatingOccupancyFraction的值到80,更快地開始老年代回收。儘管-XX:CMSInitiatingOccupancyFraction的值減少了,增大堆內存能夠避免不斷的老年代GC。在本階段,咱們得到了70ms新生代回收停頓和百分之99.9延遲80ms。

6.優化GC工做線程的任務分配

進一步縮短新生代停頓時間,咱們決定研究優化與GC線程綁定任務的選項。

-XX:ParGCCardsPerStrideChunk 選項控制GC工做線程的任務粒度,能夠幫助不使用補丁而得到最佳性能,這個補丁用來優化新生代垃圾回收的卡表掃描時間。有趣的是新生代GC時間隨着老年代空間的增長而延長。將這個選項值設爲32678,新生代回收停頓時間下降到平均50ms。此時百分之99.9應用延遲60ms。

也有其餘選項將任務映射到GC線程,若是OS容許的話,-XX:+BindGCTaskThreadsToCPUs選項綁定GC線程到個別的CPU核。-XX:+UseGCTaskAffinity使用affinity參數將任務分配給GC工做線程。然而,咱們的應用並無從這些選項發現任何益處。實際上,一些調查顯示這些選項在Linux系統不起做用[1,2]。

7.瞭解GC的CPU和內存開銷

併發GC一般會增長CPU的使用。咱們觀察了運行良好的CMS默認設置,併發GC和G1垃圾回收器共同工做引發的CPU使用增長顯著下降了應用的吞吐量和延遲。與CMS相比,G1可能佔用了應用更多的內存開銷。對於低吞吐量的非計算密集型應用,GC的高CPU使用率可能不須要擔憂。

圖2 ParNew/CMS和G1的CPU使用百分數%:相對來講CPU使用率變化明顯的節點使用G1
選項-XX:G1RSetUpdatingPauseTimePercent=20

圖3 ParNew/CMS和G1每秒服務的請求數:吞吐量較低的節點使用G1
選項-XX:G1RSetUpdatingPauseTimePercent=20

8.爲GC優化系統內存和I/O管理

一般來講,GC停頓發生在(1)低用戶時間,高系統時間和高時鐘時間和(2)低用戶時間,低系統時間和高時鐘時間。這意味着基礎的進程/OS設置存在問題。狀況(1)可能說明Linux從JVM偷頁,狀況(2)可能說明清除磁盤緩存時Linux啓動GC線程,等待I/O時線程陷入內核。在這些狀況下如何設置參數能夠參考該PPT

爲避免運行時性能損失,啓動應用時使用JVM選項-XX:+AlwaysPreTouch訪問和清零頁面。設置vm.swappiness爲零,除非在絕對必要時,OS不會交換頁面。

可能你會使用mlock將JVM頁pin在內存中,使OS不換出頁面。可是,若是系統用盡了全部的內存和交換空間,OS經過kill進程來回收內存。一般狀況下,Linux內核會選擇高駐留內存佔用但尚未長時間運行的進程(OOM狀況下killing進程的工做流)。對咱們而言,這個進程頗有可能就是咱們的應用程序。一個服務具有優雅降級(適度退化)的特色會更好,服務忽然故障預示着不太好的可操做性——所以,咱們沒有使用mlock而是vm.swappiness避免可能的交換懲罰。

LinkedIn動態信息數據平臺的GC優化

對於該平臺原型系統,咱們使用Hotspot JVM的兩個算法優化垃圾回收:

  • 新生代垃圾回收使用ParNew,老年代垃圾回收使用CMS。
  • 新生代和老年代使用G1。G1用來解決堆大小爲6GB或者更大時存在的低於0.5秒穩定的、可預測停頓時間的問題。在咱們用G1實驗過程當中,儘管調整了各類參數,但沒有獲得像ParNew/CMS同樣的GC性能或停頓時間的可預測值。咱們查詢了使用G1發生內存泄漏相關的一個bug[3],但還不能肯定根本緣由。

使用ParNew/CMS,應用每三秒40-60ms的新生代停頓和每小時一個CMS週期。JVM選項以下:

Xml代碼   收藏代碼
  1. // JVM sizing options  
  2. -server -Xms40g -Xmx40g -XX:MaxDirectMemorySize=4096m -XX:PermSize=256m -XX:MaxPermSize=256m     
  3. // Young generation options  
  4. -XX:NewSize=6g -XX:MaxNewSize=6g -XX:+UseParNewGC -XX:MaxTenuringThreshold=-XX:SurvivorRatio=8 -XX:+UnlockDiagnosticVMOptions -XX:ParGCCardsPerStrideChunk=32768  
  5. // Old generation  options  
  6. -XX:+UseConcMarkSweepGC -XX:CMSParallelRemarkEnabled -XX:+ParallelRefProcEnabled -XX:+CMSClassUnloadingEnabled  -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly     
  7. // Other options  
  8. -XX:+AlwaysPreTouch -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:-OmitStackTraceInFastThrow  
 

使用這些選項,對於幾千次讀請求的吞吐量,應用百分之99.9的延遲下降到60ms。

參考:

[1] -XX:+BindGCTaskThreadsToCPUs彷佛在Linux系統上不起做用,由於hotspot/src/os/linux/vm/os_linux.cppdistribute_processes方法在JDK7或JDK8沒有實現。
[2] -XX:+UseGCTaskAffinity選項在JDK7和JDK8的全部平臺彷佛都不起做用,由於任務的affinity屬性永遠被設置爲sentinel_worker = (uint) -1。源碼見hotspot/src/share/vm/gc_implementation/parallelScavenge/{gcTaskManager.cpp,gcTaskThread.cpp, gcTaskManager.cpp}
[3] G1存在一些內存泄露的bug,可能Java7u51沒有修改。這個bug僅在Java 8修正了。

目前服務器配置以下:

Xml代碼   收藏代碼
  1. -server -Xms1536m -Xmx1536m -XX:MaxDirectMemorySize=128m -XX:PermSize=256m -Xss256k -XX:MaxPermSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:+UseParNewGC -XX:MaxTenuringThreshold=-XX:SurvivorRatio=8 -XX:+UnlockDiagnosticVMOptions -XX:+UseConcMarkSweepGC -XX:+ParallelRefProcEnabled -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled -XX:CMSMaxAbortablePrecleanTime=500 -XX:+UseCompressedOops -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=-XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+UseCMSInitiatingOccupancyOnly -XX:+AlwaysPreTouch -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:-OmitStackTraceInFastThrow -verbose:gc -Xloggc:/log/gc/amazon-gc.log  

 

 

 另外付幾個參考配置:

http://developer.51cto.com/art/201201/312020.htm

http://blog.csdn.net/madun/article/details/7913043

 http://www.tuicool.com/articles/6Vj63qy

http://www.tuicool.com/articles/zQbmae

http://www.open-open.com/lib/view/open1399988244301.html

http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html

http://blog.sae.sina.com.cn/archives/4141

堆設置
-Xmx3550m:設置JVM最大堆內存 爲3550M。 
-Xms3550m:設置JVM初始堆內存 爲3550M。此值能夠設置與-Xmx相同,以免每次垃圾回收完成後JVM從新分配內存。 
-Xss128k: 設置每一個線程的棧 大小。JDK5.0之後每一個線程棧大小爲1M,以前每一個線程棧大小爲256K。應當根據應用的線程所需內存大小進行調整。在相同物理內存下,減少這個值能 生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在3000~5000左右。 
-Xmn2g:設置堆內存年輕代 大小爲2G。整個堆內存大小 = 年輕代大小 + 年老代大小 + 持久代大小 。持久代通常固定大小爲64m,因此增大年輕代後,將會減少年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。 
-XX:PermSize=256M:設置堆內存持久代 初始值爲256M。(貌似是Eclipse等IDE的初始化參數) 
-XX:MaxNewSize=size:新生成的對象能佔用內存的最大值。 
-XX:MaxPermSize=512M:設置持久代最大值爲512M。 
-XX:NewRatio=4:設置堆內存年輕代(包括Eden和兩個Survivor區)與堆內存年老代的比值(除去持久代) 。設置爲4,則年輕代所佔與年老代所佔的比值爲1:4。 
-XX:SurvivorRatio=4: 設置堆內存年輕代中Eden區與Survivor區大小的比值 。設置爲4,則兩個Survivor區(JVM堆內存年輕代中默認有2個Survivor區)與一個Eden區的比值爲2:4,一個Survivor區佔 整個年輕代的1/6。 
-XX:MaxTenuringThreshold=7:表示一個對象若是在救助空間(Survivor區)移動7次尚未被回收就放入年老代。 
若是設置爲0的話,則年輕代對象不通過Survivor區,直接進入年老代,對於年老代比較多的應用,這樣作能夠提升效率。 
若是將此值設置爲一個較大值,則年輕代對象會在Survivor區進行屢次複製,這樣能夠增長對象在年輕代存活時間,增長對象在年輕代即被回收的機率。 
回收器選擇
JVM給了三種選擇:串行收集器、並行收集器、併發收集器,可是串行收集器只適用於小數據量的狀況,因此這裏的選擇主要針對並行收集器和併發收集器。
默認狀況下,JDK5.0之前都是使用串行收集器,若是想使用其餘收集器須要在啓動時加入相應參數。JDK5.0之後,JVM會根據當前系統配置進行智能判斷。
串行收集器 
-XX:+UseSerialGC:設置串行收集器 
並行收集器(吞吐量優先) 
-XX:+UseParallelGC:選擇垃圾收集器爲並行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用併發收集,而年老代仍舊使用串行收集。 
-XX:ParallelGCThreads=20:配置並行收集器的線程數,即:同時多少個線程一塊兒進行垃圾回收。此值最好配置與處理器數目相等。 
-XX:+UseParallelOldGC:配置年老代垃圾收集方式爲並行收集。JDK6.0支持對年老代並行收集。 
-XX:MaxGCPauseMillis=100:設置每次年輕代垃圾回收的最長時間(單位毫秒),若是沒法知足此時間,JVM會自動調全年輕代大小,以知足此值。 
-XX:+UseAdaptiveSizePolicy:設置此選項後,並行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低響應時間或者收集頻率等。 
此參數建議使用並行收集器時,一直打開。 
併發收集器(響應時間優先) 
-XX:+UseParNewGC:設置年輕代爲併發收集。可與CMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設置,因此無需再設置此值。 
CMS, 全稱Concurrent Low Pause Collector,是jdk1.4後期版本開始引入的新gc算法,在jdk5和jdk6中獲得了進一步改進,它的主要適合場景是對響應時間的重要性需求 大於對吞吐量的要求,可以承受垃圾回收線程和應用線程共享處理器資源,而且應用中存在比較多的長生命週期的對象的應用。CMS是用於對tenured generation的回收,也就是年老代的回收,目標是儘可能減小應用的暫停時間,減小FullGC發生的概率,利用和應用程序線程併發的垃圾回收線程來 標記清除年老代。 
-XX:+UseConcMarkSweepGC:設置年老代爲併發收集。測試中配置這個之後,-XX:NewRatio=4的配置失效了。因此,此時年輕代大小最好用-Xmn設置。 
-XX:CMSFullGCsBeforeCompaction=:因爲併發收集器不對內存空間進行壓縮、整理,因此運行一段時間之後會產生「碎片」,使得運行效率下降。此參數設置運行次FullGC之後對內存空間進行壓縮、整理。 
-XX:+UseCMSCompactAtFullCollection:打開對年老代的壓縮。可能會影響性能,可是能夠消除內存碎片。 
-XX:+CMSIncrementalMode:設置爲增量收集模式。通常適用於單CPU狀況。 
-XX:CMSInitiatingOccupancyFraction=70:表示年老代空間到70%時就開始執行CMS,確保年老代有足夠的空間接納來自年輕代的對象。 
注:若是使用 throughput collector 和 concurrent low pause collector 這兩種垃圾收集器,須要適當的挺高內存大小,爲多線程作準備。
其它
-XX:+ScavengeBeforeFullGC:新生代GC優先於Full GC執行。 
-XX:-DisableExplicitGC:禁止調用System.gc(),但JVM的gc仍然有效。 
-XX:+MaxFDLimit:最大化文件描述符的數量限制。 
-XX:+UseThreadPriorities:啓用本地線程優先級API,即便 java.lang.Thread.setPriority() 生效,反之無效。 
-XX:SoftRefLRUPolicyMSPerMB=0:「軟引用」的對象在最後一次被訪問後能存活0毫秒(默認爲1秒)。 
-XX:TargetSurvivorRatio=90:容許90%的Survivor空間被佔用(默認爲50%)。提升對於Survivor的使用率——超過就會嘗試垃圾回收。 
輔助信息
-XX:-CITime:打印消耗在JIT編譯的時間 
-XX:ErrorFile=./hs_err_pid.log:保存錯誤日誌或者數據到指定文件中 
-XX:-ExtendedDTraceProbes:開啓solaris特有的dtrace探針 
-XX:HeapDumpPath=./java_pid.hprof:指定導出堆信息時的路徑或文件名 
-XX:-HeapDumpOnOutOfMemoryError:當首次遭遇內存溢出時導出此時堆中相關信息 
-XX:OnError=";":出現致命ERROR以後運行自定義命令 
-XX:OnOutOfMemoryError=";":當首次遭遇內存溢出時執行自定義命令 
-XX:-PrintClassHistogram:遇到Ctrl-Break後打印類實例的柱狀信息,與jmap -histo功能相同 
-XX:-PrintConcurrentLocks:遇到Ctrl-Break後打印併發鎖的相關信息,與jstack -l功能相同 
-XX:-PrintCommandLineFlags:打印在命令行中出現過的標記 
-XX:-PrintCompilation:當一個方法被編譯時打印相關信息 
-XX:-PrintGC:每次GC時打印相關信息 
-XX:-PrintGC Details:每次GC時打印詳細信息 
-XX:-PrintGCTimeStamps:打印每次GC的時間戳 
-XX:-TraceClassLoading:跟蹤類的加載信息 
-XX:-TraceClassLoadingPreorder:跟蹤被引用到的全部類的加載信息 
-XX:-TraceClassResolution:跟蹤常量池 
-XX:-TraceClassUnloading:跟蹤類的卸載信息 
-XX:-TraceLoaderConstraints:跟蹤類加載器約束的相關信息 
JVM服務調優實戰 
服務器:8 cup, 8G mem
e.g. 
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
調優方案:
-Xmx5g:設置JVM最大可用內存爲5G。 
-Xms5g:設置JVM初始內存爲5G。此值能夠設置與-Xmx相同,以免每次垃圾回收完成後JVM從新分配內存。 
-Xmn2g:設置年輕代大小爲2G。整個堆內存大小 = 年輕代大小 + 年老代大小 + 持久代大小 。持久代通常固定大小爲64m,因此增大年輕代後,將會減少年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。 
-XX:+UseParNewGC:設置年輕代爲並行收集。可與CMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設置,因此無需再設置此值。 
-XX:ParallelGCThreads=8:配置並行收集器的線程數,即:同時多少個線程一塊兒進行垃圾回收。此值最好配置與處理器數目相等。 
-XX:SurvivorRatio=6:設置年輕代中Eden區與Survivor區的大小比值。根據經驗設置爲6,則兩個Survivor區與一個Eden區的比值爲2:6,一個Survivor區佔整個年輕代的1/8。 
-XX:MaxTenuringThreshold=30: 設置垃圾最大年齡(次數)。若是設置爲0的話,則年輕代對象不通過Survivor區直接進入年老代。對於年老代比較多的應用,能夠提升效率。若是將此值 設置爲一個較大值,則年輕代對象會在Survivor區進行屢次複製,這樣能夠增長對象再年輕代的存活時間,增長在年輕代即被回收的機率。設置爲30表示 一個對象若是在Survivor空間移動30次尚未被回收就放入年老代。 
-XX:+UseConcMarkSweepGC:設置年老代爲併發收集。測試配置這個參數之後,參數-XX:NewRatio=4就失效了,因此,此時年輕代大小最好用-Xmn設置,所以這個參數不建議使用。 
參考資料 - JVM堆內存的分代 
虛 擬機的堆內存共劃分爲三個代:年輕代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類信息,與垃圾收集器要收集的Java對象關係不大。因此,年輕代和年老代的劃分纔是對垃圾 收集影響比較大的。
年輕代 
全部新生成的對象首先都是放在年輕代的。年輕代的目標就是儘量快速的收集掉那些生命週期短的對象。年輕代分三個區。一個Eden區,兩個 Survivor區(通常而言)。
大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到Survivor區(兩個中的一個),當一個Survivor區滿 時,此區的存活對象將被複制到另一個Survivor區,當另外一個Survivor區也滿了的時候,從前一個Survivor區複製過來的而且此時還存 活的對象,將被複制「年老區(Tenured)」。
須要注意,兩個Survivor區是對稱的,沒前後關係,因此同一個Survivor區中可能同時存在從Eden區複製過來對象,和從另外一個 Survivor區複製過來的對象;而複製到年老區的只有從前一個Survivor區(相對的)過來的對象。並且,Survivor區總有一個是空的。特 殊的狀況下,根據程序須要,Survivor區是能夠配置爲多個的(多於兩個),這樣能夠增長對象在年輕代中的存在時間,減小被放到年老代的可能。
年老代 
在年輕代中經歷了N(可配置)次垃圾回收後仍然存活的對象,就會被放到年老代中。所以,能夠認爲年老代中存放的都是一些生命週期較長的對象。
持久代 
用於存放靜態數據,如 Java Class, Method 等。持久代對垃圾回收沒有顯著影響,可是有些應用可能動態生成或者調用一些Class,例如 Hibernate 等,在這種時候須要設置一個比較大的持久代空間來存放這些運行過程當中動態增長的類型。持久代大小經過 -XX:MaxPermSize= 進行設置。

 原哥博客http://www.yuange.tech

CMSInitiatingOccupancyFraction值與Xmn的關係公式

上面介紹了promontion faild產生的緣由是EDEN空間不足的狀況下將EDEN與From survivor中的存活對象存入To survivor區時,To survivor區的空間不足,再次晉升到old gen區,而old gen區內存也不夠的狀況下產生了promontion faild從而致使full gc.那能夠推斷出:eden+from survivor < old gen區剩餘內存時,不會出現promontion faild的狀況,即:
(Xmx-Xmn)*(1-CMSInitiatingOccupancyFraction/100)>=(Xmn-Xmn/(SurvivorRatior+2))  進而推斷出:

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

例如:

當xmx=128 xmn=36 SurvivorRatior=1時 CMSInitiatingOccupancyFraction<=((128.0-36)-(36-36/(1+2)))/(128-36)*100 =73.913

當xmx=128 xmn=24 SurvivorRatior=1時 CMSInitiatingOccupancyFraction<=((128.0-24)-(24-24/(1+2)))/(128-24)*100=84.615…

當xmx=3000 xmn=600 SurvivorRatior=1時  CMSInitiatingOccupancyFraction<=((3000.0-600)-(600-600/(1+2)))/(3000-600)*100=83.33

CMSInitiatingOccupancyFraction低於70% 須要調整xmn或SurvivorRatior值。

 

 

一:理解GC日誌格式,讀GC日誌的方法

1:開啓日誌

-verbose:gc 

-XX:+PrintGCDetails 

-XX:+PrintGCDateStamps

-Xloggc:/path/gc.log

-XX:+UseGCLogFileRotation  啓用GC日誌文件的自動轉儲 (Since Java)

-XX:NumberOfGClogFiles=1  GC日誌文件的循環數目 (Since Java)

-XX:GCLogFileSize=1M  控制GC日誌文件的大小 (Since Java)

-XX:+PrintGC包含-verbose:gc

-XX:+PrintGCDetails //包含-XX:+PrintGC

只要設置-XX:+PrintGCDetails 就會自動帶上-verbose:gc和-XX:+PrintGC

-XX:+PrintGCDateStamps/-XX:+PrintGCTimeStamps 輸出gc的觸發時間

 


 2:新生代(Young GC)gc日誌分析

Java代碼   收藏代碼
  1. 2014-02-28T11:59:00.638+0800: 766.537:[GC2014-02-28T11:59:00.638+0800: 766.537:  
  2. [ParNew: 1770882K->212916K(1835008K), 0.0834220 secs]  
  3. 5240418K->3814487K(24903680K), 0.0837310 secs] [Times: user=1.12 sys=0.02, real=0.08 secs]  

  2014-02-28T11:59:00 ...(時間戳):[GC(Young GC)(時間戳):[ParNew(使用ParNew做爲年輕代的垃圾回收期):

1770882K(年輕代垃圾回收前的大小)->212916K(年輕代垃圾回收之後的大小)(1835008K)(年輕代的capacity), 0.0834220 secs(回收時間)]
5240418K(整個heap垃圾回收前的大小)->3814487K(整個heap垃圾回收後的大小)(24903680K)(heap的capacity), 0.0837310secs(回收時間)]
[Times: user=1.12(Young GC用戶耗時) sys=0.02(Young GC系統耗時), real=0.08 secs(Young GC實際耗時)]

   其中 Young GC回收了1770882-212916=1557966K內存
Heap經過此次回收總共減小了 5240418-3814487=1425931 K的內存。1557966-1425931=132035K說明此次Young GC有約128M的內存被移動到了Old Gen,
 提示:進代量(Young->Old)須要重點觀察,預防promotion failed.

 

 3:老年代(CMS old gc)分析

Java代碼   收藏代碼
  1. 2014-02-28T23:58:42.314+0800: 25789.661: [GC [1 CMS-initial-mark: 17303356K(23068672K)] 18642315K(24903680K), 1.0400410 secs] [Times: user=1.04 sys=0.00, real=1.04 secs]  
  2. 2014-02-28T23:58:43.354+0800: 25790.701: [CMS-concurrent-mark-start]  
  3. 2014-02-28T23:58:43.717+0800: 25791.064: [CMS-concurrent-mark: 0.315/0.363 secs] [Times: user=1.64 sys=0.02, real=0.37 secs]  
  4. 2014-02-28T23:58:43.717+0800: 25791.064: [CMS-concurrent-preclean-start]  
  5. 2014-02-28T23:58:43.907+0800: 25791.254: [CMS-concurrent-preclean: 0.181/0.190 secs] [Times: user=0.20 sys=0.01, real=0.19 secs]  
  6. 2014-02-28T23:58:43.907+0800: 25791.254: [CMS-concurrent-abortable-preclean-start]  
  7.  CMS: abort preclean due to time 2014-02-28T23:58:49.082+0800: 25796.429: [CMS-concurrent-abortable-preclean: 5.165/5.174 secs] [Times: user=5.40 sys=0.04, real=5.17 secs]  
  8. 2014-02-28T23:58:49.083+0800: 25796.430: [GC[YG occupancy: 1365142 K (1835008 K)]2014-02-28T23:58:49.083+0800: 25796.430: [Rescan (parallel) , 0.9690640 secs]2014-02-28T23:58:50.052+0800: 25797.399: [weak refs processing, 0.0006190 secs]2014-02-28T23:58:50.053+0800: 25797.400: [scrub string table, 0.0006290 secs] [1 CMS-remark: 17355150K(23068672K)] 18720292K(24903680K), 0.9706650 secs] [Times: user=16.49 sys=0.06, real=0.97 secs]  
  9. 2014-02-28T23:58:50.054+0800: 25797.401: [CMS-concurrent-sweep-start]  
  10. 2014-02-28T23:58:51.940+0800: 25799.287: [CMS-concurrent-sweep: 1.875/1.887 secs] [Times: user=2.03 sys=0.03, real=1.89 secs]  
  11. 2014-02-28T23:58:51.941+0800: 25799.288: [CMS-concurrent-reset-start]  
  12. 2014-02-28T23:58:52.067+0800: 25799.414: [CMS-concurrent-reset: 0.127/0.127 secs] [Times: user=0.13 sys=0.00, real=0.13 secs]  
  13. 2014-03-01T00:00:36.293+0800: 25903.640: [GC2014-03-01T00:00:36.293+0800: 25903.640: [ParNew: 1805234K->226801K(1835008K), 0.1020510 secs] 10902912K->9434796K(24903680K), 0.1023150 secs] [Times: user=1.35 sys=0.02, real=0.10 secs]  
  14. 2014-03-01T00:07:13.559+0800: 26300.906: [GC2014-03-01T00:07:13.559+0800: 26300.906: [ParNew: 1799665K->248991K(1835008K), 0.0876870 secs] 14086673K->12612462K(24903680K), 0.0879620 secs] [Times: user=1.24 sys=0.01, real=0.09 secs]  

 CMS的gc日誌分爲一下幾個步驟,重點關注initial-mark和remark這兩個階段,由於這兩個階段會stop進程。

初始標記(init mark):收集根引用,這是一個stop-the-world階段。

併發標記(concurrent mark):這個階段能夠和用戶應用併發進行。遍歷老年代的對象圖,標記出活着的對象。

併發預清理(concurrent preclean):這一樣是一個併發的階段。主要的用途也是用來標記,用來標記那些在前面標記以後,發生變化的引用。主要是爲了縮短remark階段的stop-the-world的時間。

從新標記(remark):這是一個stop-the-world的操做。暫停各個應用,統計那些在發生變化的標記。

併發清理(concurrent sweep):併發掃描整個老年代,回收一些在對象圖中不可達對象所佔用的空間。

併發重置(concurrent reset):重置某些數據結果,以備下一個回收週期

提示:紅色爲所有暫停階段重點關注.

 

Java代碼   收藏代碼
  1. [GC [1 CMS-initial-mark: 17303356K(23068672K)] 18642315K(24903680K), 1.0400410 secs] [Times: user=1.04 sys=0.00, real=1.04 secs]  

 其中數字依表示標記先後old區的全部對象佔內存大小和old的capacity,整個JavaHeap(不包括perm)全部對象佔內存總的大小和JavaHeap的capacity。

 

Java代碼   收藏代碼
  1. [GC[YG occupancy: 1365142 K (1835008 K)]2014-02-28T23:58:49.083+0800: 25796.430:   
  2. [Rescan (parallel) , 0.9690640 secs]2014-02-28T23:58:50.052+0800: 25797.399:   
  3. [weak refs processing, 0.0006190 secs]2014-02-28T23:58:50.053+0800: 25797.400: [scrub string table, 0.0006290 secs]   
  4. [1 CMS-remark: 17355150K(23068672K)] 18720292K(24903680K), 0.9706650 secs] [Times: user=16.49 sys=0.06, real=0.97 secs]  

 Rescan (parallel)表示的是多線程處理young區和多線程掃描old+perm的總時間, parallel 表示多GC線程並行。

weak refs processing 處理old區的弱引用的總時間,用於回收native memory.等等


參考資料:

 https://blogs.oracle.com/jonthecollector/entry/the_unspoken_cms_and_printgcdetails

 https://blogs.oracle.com/poonam/entry/understanding_cms_gc_logs

 原哥博客http://www.yuange.tech

4:老年代(CMS old GC ) concurrent mode failure日誌

Java代碼   收藏代碼
  1. 2014-03-03T09:38:26.457+0800: 233373.804: [GC [1 CMS-initial-mark: 17319615K(23068672K)] 17351070K(24903680K), 0.0419440 secs]   
  2. [Times: user=0.04 sys=0.00, real=0.04 secs]  
  3. 2014-03-03T09:38:26.499+0800: 233373.846: [CMS-concurrent-mark-start]  
  4. 2014-03-03T09:38:28.175+0800: 233375.522: [GC2014-03-03T09:38:28.175+0800: 233375.522: [CMS2014-03-03T09:38:28.887+0800: 233376.234:   
  5. [CMS-concurrent-mark: 1.989/2.388 secs] [Times: user=14.37 sys=0.24, real=2.39 secs]  
  6.  (concurrent mode failure): 17473174K->8394653K(23068672K), 19.3309170 secs] 18319691K->8394653K(24903680K),   
  7.  [CMS Perm : 23157K->23154K(98304K)], 19.3311700 secs] [Times: user=22.18 sys=0.00, real=19.33 secs]  

 concurrent mode failure通常發生在CMS GC 運行過程當中,老年代空間不足,引起MSC(Full GC)

上面的這條發日誌說明CMS運行到CMS-concurrent-mark過程當中就出現空間不足,產生併發失敗(17319615K(23068672K)佔77%),


解決思路:下降YGC頻率,下降CMS GC觸發時機,適當下降CMSInitiatingOccupancyFraction.

 

5:新生代(ParNew YGC)promotion failed日誌 

Java代碼   收藏代碼
  1. 2014-02-27T21:19:42.460+0800: 210095.040: [GC 210095.040: [ParNew (promotion failed): 1887487K->1887488K(1887488K), 0.4818790 secs]210095.522: [CMS: 13706434K->7942818K(23068672K), 9.7152990 secs] 15358303K->7942818K(24956160K), [CMS Perm : 27424K->27373K(98304K)], 10.1974110 secs] [Times: user=12.06 sys=0.01, real=10.20 secs]  

 promotion failed通常發生在新生代晉升老年代時,老年代空間不夠或連續空間不夠卻還沒達到old區的觸發值,引起Full Gc.

 

解決思路:因爲heap碎片,YGC晉升對象過大,過長.(mid/long Time Object),調整-XX:PretenureSizeThreshold=65535,-XX:MaxTenuringThreshold=6

 

6:system.gc()產生的Full GC日誌

Java代碼   收藏代碼
  1. <strong>2014-01-21T17:44:01.554+0800: 50212.568: [Full GC (System) 50212.568:   
  2. [CMS: 943772K220K(2596864K), 2.3424070 secs] 1477000K->220K(4061184K), [CMS Perm : 3361K->3361K(98304K)], 2.3425410 secs] [Times: user=2.33 sys=0.01, real=2.34 secs]</strong>  

 

Full GC (System)意味着這是個system.gc調用產生的MSC。
「943772K->220K(2596864K), 2.3424070 secs」表示:此次MSC先後old區內總對象大小,old的capacity及此次MSC耗時。
「1477000K->220K(4061184K)」表示:此次MSC先後JavaHeap內總對象大小,JavaHeap的capacity。
「3361K->3361K(98304K)], 2.3425410 secs」表示:此次MSC先後Perm區內總對象大小,Perm區的capacity。


解決:
使用-XX:+DisableExplicitGC參數,System.gc()會變成空調用.
若是應用有地方大量使用direct memory 或 rmi,那麼使用-XX:+DisableExplicitGC要當心。
可使用-XX:+ExplicitGCInvokesConcurrent替換把 System.gc()從Full GC換成CMS GC.

 

緣由:
DirectByteBuffer沒有finalizer,native memory的清理工做是經過sun.misc.Cleaner自動完成
sun.misc.Cleaner是基於PhantomReference的清理工具,Full GC/Old GC會對old gen作reference processing,同時觸發Cleaner對已死的DirectByteBuffer對象作清理。
若是長時間沒有GC或者只作了young GC的話,不會觸發old區Cleaner的工做,容易產生DirectMemory OOM.
參考:https://gist.github.com/rednaxelafx/1614952

原哥博客http://www.yuange.tech

RMI會作的是分佈式GC。Sun JDK的分佈式GC是用純Java實現的,爲RMI服務。 
參考:http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/sunrmiproperties.html

 

7:特殊的Full GC日誌,根據動態計算直接進行MSC

Java代碼   收藏代碼
  1. 2014-02-13T13:48:06.349+0800: 7.092: [GC 7.092: [ParNew: 471872K->471872K(471872K), 0.0000420 secs]7.092: [CMS: 366666K->524287K(524288K), 27.0023450 secs] 838538K->829914K(996160K), [CMS Perm : 3196K->3195K(131072K)], 27.0025170 secs]  

 ParNew的時間特別短,jvm在minor gc前會首先確認old是否是足夠大,若是不夠大,此次young gc直接返回,進行MSC(Full GC)。


二:參數配置和理解

1:參數分類和說明

jvm參數分固定參數和非固定參數

1):固定參數

如:-Xmx,-Xms,-Xmn,-Xss.

2):非固定參數

如:

-XX:+<option> 啓用選項

-XX:-<option> 不啓用選項

-XX:<option>=<number> 給選項設置一個數字類型值,可跟單位,例如 128k, 2g

-XX:<option>=<string> 給選項設置一個字符串值,例如-XX:HeapDumpPath=./dump.log

  原哥博客http://www.yuange.tech

2:JVM可設置參數和默認值

1):-XX:+PrintCommandLineFlags

打印出JVM初始化完畢後全部跟最初的默認值不一樣的參數及它們的值,jdk1.5後支持.

線上建議打開,能夠看到本身改了哪些值.

2):-XX:+PrintFlagsFinal

顯示全部可設置的參數及"參數處理"後的默認值。參數自己只從JDK6 U21後支持

但是查看不一樣版本默認值,以及是否設置成功.輸出的信息中"="表示使用的是初始默認值,而":="表示使用的不是初始默認值

如:jdk6/7 -XX:+MaxTenuringThreshold 默認值都是15,可是在使用CMS收集器後,jdk6默認4 , jdk7默認6.

 

Java代碼   收藏代碼
  1. [hbase96 logs]# java -version  
  2. java version "1.6.0_27-ea"  
  3. [hbase96 logs]# java -XX:+PrintFlagsInitial | grep MaxTenuringThreshold  
  4. intx MaxTenuringThreshold = 15 {product}  
  5. [hbase96 logs]# java -XX:+PrintFlagsFinal -XX:+UseConcMarkSweepGC | grep MaxTenuringThreshold   
  6. intx MaxTenuringThreshold := 4 {product}   
  7.   
  8. [zw-34-71 logs]# java -version  
  9. java version "1.7.0_45"  
  10. [zw-34-71 logs]# java -XX:+PrintFlagsInitial | grep MaxTenuringThreshold  
  11. intx MaxTenuringThreshold = 15 {product}  
  12. [zw-34-71 logs]# java -XX:+PrintFlagsFinal -XX:+UseConcMarkSweepGC | grep MaxTenuringThreshold  
  13. intx MaxTenuringThreshold := 6 {product}  

 

3):-XX:+PrintFlagsInitial

 在"參數處理"以前全部可設置的參數及它們的值,而後直接退出程序.

這裏的"參數處理"指: 檢查參數之間是否有衝突,經過ergonomics調整某些參數的值等.

 

Java代碼   收藏代碼
  1. [hbase96 logs]# java -version  
  2. java version "1.6.0_27-ea"  
  3. [hbase96 logs]# java -XX:+PrintFlagsInitial | grep UseCompressedOops  
  4. bool UseCompressedOops = false {lp64_product}   
  5. [hbase96 logs]# java -XX:+PrintFlagsFinal | grep UseCompressedOops  
  6. bool UseCompressedOops := true {lp64_product}  

 

4)CMSInitiatingOccupancyFraction 默認值是多少 

jdk6/7:

Java代碼   收藏代碼
  1. #java -server -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal | grep -P "CMSInitiatingOccupancyFraction|CMSTriggerRatio|MinHeapFreeRatio"  
  2. intx CMSInitiatingOccupancyFraction            = -1              {product}             
  3. intx CMSTriggerRatio                           = 80              {product}             
  4. uintx MinHeapFreeRatio                          = 40              {product}  

 

計算公式:

CMSInitiatingOccupancyFraction = (100 - MinHeapFreeRatio) + (CMSTriggerRatio * MinHeapFreeRatio / 100)

最終結果: 在jdk6/7中 CMSInitiatingOccupancyFraction默認值是92% .不是網上傳的68%;  都這麼傳,是由於 "深刻理解Java虛擬機"一書中是68%,但它用的是jdk5 , jdk5的CMSTriggerRatio默認值是20,坑爹...

 

三:JVM內存區域理解和相關參數

一圖勝千言,直接上圖

1):物理分代圖.

 

物理分代是除G1以外的JVM 內存分配方式,jvm 經過-Xmx,-Xmn/newRatio等參數將jvm heap劃分紅物理固定大小,對於不一樣場景比例應該設置成多少很考驗經驗.

一篇JVM CMS優化講解的很是好的文章: how-tame-java-gc-pauses

 

2) 邏輯分代圖(G1)

邏輯分代是之後的趨勢(PS:jkd8連perm都不區分了。), 不須要使用者在糾結Xms/Xmn,SurvivorRatio等比例問題,採用動態算法調整分代大小。

相關文章
相關標籤/搜索