熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

JDK5.0垃圾收集優化之--Dont Pause

2022-06-13   來源: Java核心技術 

  作者簡介肖桦江南白衣開源項目SpringSide春天的旁邊發起者

  轉自

  原本想把題目更簡單的定為《不要停》的但還是自己YY一下就算了
       Java開發Server最大的障礙就是JDK版之前的的串行垃圾收集機制會引起長時間的服務暫停明白原理後想想那些用JDK寫Server的先輩不得不後怕
      好在JDK已開始支持多線程並行的後台垃圾收集算法JDK則優化了默認值的設置

  一參考資料

  Tuning Garbage Collection with the Java Virtual Machine 官方指南
    Hotspot memory management whitepaper 官方白皮書
    Java Tuning White Paper 官方文檔
    FAQ about Garbage Collection in the Hotspot  官方FAQJVM
    Java HotSpot 虛擬機中的垃圾收集 JavaOne上的中文ppt
    A Collection of JVM Options JVM選項的超完整收集
    二基本概念

  堆(Heap)

  JVM管理的內存叫堆Bit操作系統上有GG的限制Bit的就沒有

  JVM初始分配的內存由Xms指定默認是物理內存的/但小於G

  JVM最大分配的內存由Xmx指定默認是物理內存的/但小於G

  默認空余堆內存小於%時JVM就會增大堆直到Xmx的最大限制可以由XX:MinHeapFreeRatio=指定 默認空余堆內存大於%時JVM會減少堆直到Xms的最小限制可以由XX:MaxHeapFreeRatio=指定

  服務器一般設置XmsXmx相等以避免在每次GC 後調整堆的大小所以上面的兩個參數沒啥用

  基本收集算法

  復制將堆內分成兩個相同空間從根(ThreadLocal的對象靜態對象)開始訪問每一個關聯的活躍對象將空間A的活躍對象全部復制到空間B然後一次性回收整個空間A因為只訪問活躍對象將所有活動對象復制走之後就清空整個空間不用去訪問死對象所以遍歷空間的成本較小但需要巨大的復制成本和較多的內存
    標記清除(marksweep)收集器先從根開始訪問所有活躍對象標記為活躍對象然後再遍歷一次整個內存區域把所有沒有標記活躍的對象進行回收處理該算法遍歷整個空間的成本較大暫停時間隨空間大小線性增大而且整理後堆裡的碎片很多
    標記整理(marksweepcompact)綜合了上述兩者的做法和優點先標記活躍對象然後將其合並成較大的內存塊
        可見沒有免費的午餐無論采用復制還是標記清除算法自動的東西都要付出很大的性能代價

  分代

  分代是Java垃圾收集的一大亮點根據對象的生命周期長短把堆分為個代YoungOld和Permanent根據不同代的特點采用不同的收集算法揚長避短也

  Young(Nursery)年輕代研究表明大部分對象都是朝生暮死隨生隨滅的因此所有收集器都為年輕代選擇了復制算法     復制算法優點是只訪問活躍對象缺點是復制成本高因為年輕代只有少量的對象能熬到垃圾收集因此只需少量的復制成本而且復制收集器只訪問活躍對象對那些占了最大比率的死對象視而不見充分發揮了它遍歷空間成本低的優點

  Young的默認值為M隨堆內存增大約為/JVM會根據情況動態管理其大小變化     XX:NewRatio= 參數可以設置Young與Old的大小比例server時默認為:但實際上young啟動時遠低於這個比率?如果信不過JVM也可以用Xmn硬性規定其大小有文檔推薦設為Heap總大小的/

  Young的大小非常非常重要後面暫停時間優先收集器的論述

  Young裡面又分為個區域一個Eden所有新建對象都會存在於該區兩個Survivor區用來實施復制算法每次復制就是將Eden和第一塊Survior的活對象復制到第然後清空Eden與第一塊SurviorEden與Survivor的比例由XX:SurvivorRatio=設置默認為Survivio大了會浪費小了的話會使一些年輕對象潛逃到老人區引起老人區的不安但這個參數對性能並不重要

  Old(Tenured)年老代年輕代的對象如果能夠挺過數次收集就會進入老人區老人區使用標記整理算法因為老人區的對象都沒那麼容易死的采用復制算法就要反復的復制對象很不合算只好采用標記清理算法但標記清理算法其實也不輕松每次都要遍歷區域內所有對象所以還是沒有免費的午餐啊

  XX:MaxTenuringThreshold=設置熬過年輕代多少次收集後移入老人區CMS中默認為熬過第一次GC就轉入可以用XX:+PrintTenuringDistribution查看

  Permanent持久代裝載Class信息等基礎數據默認M如果是類很多很多的服務程序需要加大其設置XX:MaxPermSize=否則它滿了之後會引起fullgc()或Out of Memory 注意SpringHibernate這類喜歡AOP動態生成類的框架需要更多的持久代內存

  minor/major collection

  每個代滿了之後都會促發collection(另外Concurrent Low Pause Collector默認在老人區%的時候促發)GC用較高的頻率對young進行掃描和回收這種叫做minor collection 而因為成本關系對Old的檢查回收頻率要低很多同時對Young和Old的收集稱為major collection     Systemgc()會引發major collection使用XX:+DisableExplicitGC禁止它或設為CMS並發XX:+ExplicitGCInvokesConcurrent

  小結

  Young minor collection 復制算法

  Old(Tenured) major colletion 標記清除/標記整理算法

  三收集器

  古老的串行收集器(Serial Collector)

  使用 XX:+UseSerialGC策略為年輕代串行復制年老代串行標記整理

  吞吐量優先的並行收集器(Throughput Collector)

  使用 XX:+UseParallelGC 也是JDK server的默認值策略為     年輕代暫停應用程序多個垃圾收集線程並行的復制收集線程數默認為CPU個數CPU很多時可用–XX:ParallelGCThreads=減少線程數     年老代暫停應用程序與串行收集器一樣單垃圾收集線程標記整理

  所以需要+的CPU時才會優於串行收集器適用於後台處理科學計算

  可以使用XX:MaxGCPauseMillis= 和 XX:GCTimeRatio 來調整GC的時間

  暫停時間優先的並發收集器(Concurrent Low Pause CollectorCMS)

  前面說了這麼多都是為了這節做鋪墊

  使用XX:+UseConcMarkSweepGC策略為     年輕代同樣是暫停應用程序多個垃圾收集線程並行的復制收集     年老代則只有兩次短暫停其他時間應用程序與收集線程並發的清除

   年老代詳述

  並行(Parallel)與並發(Concurrent)僅一字之差並行指多條垃圾收集線程並行並發指用戶線程與垃圾收集線程並發程序在繼續運行而垃圾收集程序運行於另一個個CPU上

  並發收集一開始會很短暫的停止一次所有線程來開始初始標記根對象然後標記線程與應用線程一起並發運行最後又很短的暫停一次多線程並行的重新標記之前可能因為並發而漏掉的對象然後就開始與應用程序並發的清除過程可見最長的兩個遍歷過程都是與應用程序並發執行的比以前的串行算法改進太多太多了!!!

  串行標記清除是等年老代滿了再開始收集的而並發收集因為要與應用程序一起運行如果滿了才收集應用程序就無內存可用所以系統默認%滿的時候就開始收集內存已設得較大吃內存又沒有這麼快的時候可以用XX:CMSInitiatingOccupancyFraction=恰當增大該比率

   年輕代詳述

  可惜對年輕代的復制收集依然必須停止所有應用程序線程原理如此只能靠多CPU多收集線程並發來提高收集速度但除非你的Server獨占整台服務器否則如果服務器上本身還有很多其他線程時切換起來速度就 所以搞到最後暫停時間的瓶頸就落在了年輕代的復制算法上

  因此Young的大小設置挺重要的大點就不用頻繁GC而且增大GC的間隔後可以讓多點對象自己死掉而不用復制了但Young增大時GC造成的停頓時間攀升得非常恐怖比如在我的機器上默認M的Young只需要幾毫秒的時間M就升到毫秒而升到M時就要到毫秒了峰值還會攀到恐怖的ms誰叫復制算法要等Young滿了才開始收集開始收集就要停止所有線程呢

   持久代

  可設置XX:+CMSClassUnloadingEnabled XX:+CMSPermGenSweepingEnabled使CMS收集持久代的類而不是fullgcnetbeans performance文檔的推薦

  增量(train算法)收集器(Incremental Collector)

  已停止維護–Xincgc選項默認轉為並發收集器

  四暫停時間顯示

  加入下列參數 (請將PrintGC和Details中間的空格去掉CSDN很怪的認為是禁止字句)

  verbose:gc XX:+PrintGC Details  XX:+PrintGCTimeStamps

  會程序運行過程中將顯示如下輸出

  : [GC : [ParNew: K>K(K) secs] K>K(K) secs]

  顯示在程序運行的秒發生了Minor的垃圾收集前一段數據針對新生區k整理為k新生區總大小為k程序暫停了ms而後一段數據針對整個堆

  對於年老代的收集暫停發生在下面兩個階段CMSremark的中斷是毫秒

  [GC [ CMSinitialmark: K(K)] K(K) secs]

  [ CMSremark: K(K)] K(K) secs]

  再加兩個參數 XX:+PrintGCApplicationConcurrentTime XX:+PrintGCApplicationStoppedTime對暫停時間看得更清晰

  五真正不停的BEA JRockit 與Sun RTS

  Bea的JRockit R 的特色之一是動態決定的垃圾收集策略用戶可以決定自己關心的是吞吐量暫停時間還是確定的暫停時間再由JVM在運行時動態決定改變改變垃圾收集策略        它的Deterministic GC的選項是Xgcprio: deterministic號稱可以把暫停可以控制在毫秒非常的牛一句Deterministic道盡了RealTime的真谛 不過細看一下文檔ms的測試環境是 GB heap 和 平均  % 的活躍對象(也就是M)活動對象 個 Xeon GHz  G內存 或者是 個Xeon GHzG內存

  最可惜JRockt的license很奇怪雖然平時使用免費但這個ms的選項就需要購買整個Weblogic Real Time Server的license

  其他免費選項

  Xgcprio:pausetime Xpausetarget=ms    因為免費所以最低只能設置到ms pause target ms是Sun認為RealTime的分界線
    Xgc:gencon 普通的並發做法效率也不錯
      JavaOne上有Sun的 Java RealTime System 的介紹RTS基於JDK在RealTime  Garbage Collctor上又有改進但還在beta版狀態只供給OEM更怪

  六JDK 的改進

  因為JDK在Young較大時的表現還是不夠讓人滿意又繼續看JDK的改進結果稍稍失望不涉及我最頭痛的年輕代復制收集改良

  年老代的標識清除收集並行執行標識   JDK只開了一條收集進程與應用線程並發標識可以開多條收集線程來做標識縮短標識老人區所有活動對象的時間

  加大了Young區的默認大小 默認大小從M加到M從堆內存的/增加到/

  Systemgc()可以與應用程序並發執行 使用XX:+ExplicitGCInvokesConcurrent 設置

  七小結

   JDK/

  對於服務器應用我們使用Concurrent Low Pause Collector對年輕代暫停時多線程並行復制收集對年老代收集器與應用程序並行標記整理收集以達到盡量短的垃圾收集時間

  本著沒有深刻測試前不要胡亂優化的宗旨命令行屬性只需簡單寫為

  server Xms<heapsize>M Xmx<heapsize>M XX:+UseConcMarkSweepGC  XX:+PrintGC Details  XX:+PrintGCTimeStamps
    然後要根據應用的情況在測試軟件輔助可以下看看有沒有JVM的默認值和自動管理做的不夠的地方可以調整xmn 設Young的大小XX:MaxPermSize設持久代大小等

   JRockit R

  但因為JDK的測試結果實在不能滿意後來又嘗試了JRockit總體效果要好些  JRockit的特點是動態垃圾收集器是根據用戶關心的特征動態決定收集算法的參數如下

  Xms<heapsize>M Xmx<heapsize>M Xgcprio:pausetime Xpausetarget=ms XgcReport XgcPause Xverbose:memory


From:http://tw.wingwit.com/Article/program/Java/hx/201311/27195.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.