首頁 > Java > java教程 > 主體

詳解為任務關鍵型Java應用優化垃圾回收(下)

黄舟
發布: 2017-03-23 11:03:44
原創
1056 人瀏覽過

為任務關鍵型Java應用優化垃圾回收(上)

並行標記清除(CMS)回收器

CMS垃圾回收器是第一個廣泛使用的低延遲回收器。雖然在Java 1.4.2中就可以使用了,但剛開始還不是很穩定。這些問題直到Java 5才得以解決。

從CMS回收器的名稱就可以看出它使用並行方式:大部分回收工作由一個GC執行緒完成,與處理使用者請求的工作執行緒並行執行。老年代原來單一的stop-the-world回收過程被劃分為兩個較短的stop-the-world暫停加上5個並行階段。在這些並行階段中,原來的工作執行緒照常運作(不會被暫停)。

使用下面的參數可以啟動CMS回收器:

-XX:+UseConcMarkSweepGC
登入後複製

再次應用到上面的測試程式(並提高負載)可以得到以下結果:

圖4 最佳化堆疊大小並使用CMS的JVM在50小時內的GC行為(-Xms1200m -Xmx1200m -XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6 -XX:+UseConcMarkSweepGC ))

可以看到,老年代GC的8s左右暫停已經消失了。現在,老年代回收過程只出現兩次暫停(前一次的結果50小時內有5次),並且所有暫停都在1s內。

預設情況下,CMS回收器使用ParNew(GC演算法)處理新生代回收。如果ParNew和CMS一起運行,它的暫停會比沒有CMS時長一點,因為他們之間需要額外的協同工作。與上次的測試結果相比,可以從新生代的平均暫停時間略有上升發現這個問題。新生代暫停時間中離群值頻繁出現,從這裡也可以發現這個問題。離群值可達0.5s左右。但是這些暫停對許多應用來說已經夠短了,所以CMS/ParNew組合可以作為一個很好的低延遲最佳化選擇。

CMS回收器的一個嚴重缺陷是,當老年代空間都被佔滿時CMS無法啟動。一旦老年代被佔滿了,啟動CMS就太晚了;虛擬機必須使用通常的“stop-the-world”策略(在GC日誌中會出現“concurrent mode failure”的記錄) 。為了實現低延遲目標,當老年代空間佔用量達到一定閘限值時,就應該啟動CMS回收器,透過以下設定來實現:

-XX:CMSInitiatingOccupancyFraction=80
登入後複製

這表示一旦老年代空間被佔用80%時,CMS回收器就會運作。對於我們的應用,使用這個值(也就是預設值)就可以。但如果把門限值設太高的話,就會產生“concurrent mode failure”,導致長時間的老年代GC暫停。反過來,如果設的太低(低於活躍空間大小),CMS可能一直並行運行,導致某個CPU核心完全用在GC上。如果一個應用的物件創建和堆疊使用行為變化很快,例如透過互動的方式或計時器啟動專門的任務,很難設定一個合適的閘限值同時避免上述兩種問​​題。

碎片的陰影

然而,CMS最大的一個問題是它不會整理舊年代堆空間。這樣會產生堆碎片,隨著時間運行,會導致服務嚴重惡化。有兩個因素會導致這種情況:緊缺的老年代空間大小,以及頻繁的CMS回收。第一個因素可以透過增加老年代堆空間來改善,要大於ParallelGC回收器所需的空間(我從1024M增加到1200M,從前幾幅圖可以看到)。第二個問題可以透過適當地分​​割各代空間來優化,前面講過。我們可以實際看一下這樣可以把老年代GC的頻率降低多少。

為了證明使用CMS前合理地調整各代堆大小很重要,我們先看看如果不遵守上述的原則,在圖1(幾乎不對堆做優化)的基礎上直接使用CMS回收器會怎麼樣:

圖5 未最佳化堆疊大小的GC行為,以及使用CMS後記憶體碎片導致的效能惡化(從第14小時開始)

很明顯,JVM在這樣設定的負載測試下可以穩定地工作將近14個小時(在生產環境以及更小的負載條件下,這個不穩定的良性階段可能會持續更久)。接下來,突然會出現多次很長的GC暫停,暫停時間幾乎佔剩餘時間的一半。不僅老年代的暫停時間會達到10s以上,新生代的暫停時間也會達到數秒。因為回收器為了將新生代的物件移到老年代,需要耗費很長的時間搜尋老年代空間。

CMS低延迟优点的代价就是内存碎片。这个问题可以最小化,但是不会彻底消失。你永远不知道它什么时候会被触发。然而,通过合理的优化与监控可以控制它的风险。

G1(Garbage First)回收器的希望

G1回收器设计的目的就是保证低延迟的同时而没有堆碎片风险。因此,Oracle把它作为CMS的一个长期取代。G1可以避免碎片风险是因为它会整理堆空间。对于GC暂停来说,G1的目标并不是使暂停时间最小化,而是设置一个时间上限,使GC暂停尽量满足这一上限值。

在将G1回收器用于测试程序中并与上述其他经典回收器做对比之前,先总结两点关于G1的重要信息。

  • Oracle在Java 7u4中开始支持G1。为了使用G1你应该将Java 7更新到最新。Oracle的GC团队一直致力于G1的研发,在最新的Java更新中(本文编写时最新版本是7u7到7u9),G1的改进很显著。另一方面,G1无法在任何Java 6版本中使用,而且到目前更优越的Java 7不可能向后移植到Java 6中。

  • 前面关于调节各代空间大小的优化对G1来说已经淘汰了。设置各代空间大小与设置暂停目标时间相冲突会使G1回收器偏离原本的设计目标。使用G1时,可以使用“-Xms”和“-Xmx”设置整体的内存大小,也可以设置GC暂停目标时间(可选),对G1来说不用设置其他选项。与ParallelGC回收器的AdapativeSizingPolicy类似,它自适应地调整各代空间大小来满足暂停目标时间。

遵循这些原则后,G1回收器在默认配置下的结果如下:

图6 最小配置(-Xms1024m -Xmx1024 -XX:+UseG1GC)的JVM在G1下26小时内的GC性能

在这个例子中,我们使用了默认的GC暂停目标时间200ms。从图中可以看到,平均时间与这个目标比较吻合,最长GC暂停时间与使用CMS回收器差不多(图4)。G1明显可以很好地控制GC暂停,与平均时长相比,离群值也相当少。

另一方面,平均GC暂停时间要比CMS回收器长很多(270 vs 100ms),而且更频繁。这意味着GC累积暂停时间(也就是GC本身所占总时间)是使用CMS的4倍以上(6.96% vs 1.66%)。

与CMS一样,G1也分为GC暂停阶段和并行回收阶段(不暂停任务)。同样与CMS类似,当堆占用比达到一定门限后,它才启动并行回收阶段。从图6可以看到,1GB的可用内存到目前为止并没有完全使用。这是因为G1的默认占用比门限值要比CMS低很多。也有人指出,一般来说较小的堆空间就可以满足G1的需求。

垃圾回收器的定量比较

下面的表格总结了Oracle Java 7中4种最重要的垃圾回收器在测试中的关键性能指标。在同样的应用程序上,进行相同的负载测试,但是负载的级别不同(由第2列的垃圾创建速率体现)。

表 几种垃圾回收器的比较

所有的回收器都运行在1GB的堆空间上。传统的回收器(ParallelGC、ParNewGC和CMS)另外使用下面的堆设置:

-XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6
登入後複製

而G1回收器没有额外的堆大小设置,并且使用默认的暂停目标时间200ms,也可以显示设置:

-XX:MaxGCPauseMillis=200
登入後複製

从表中可以看到,传统回收器在新生代回收上(第3列)时间差不多。对ParallelGC和ParNewGC来说是差不多的,而CMS实际上也是使用ParNewGC去回收新生代。然而,在新生代GC暂停中,将新生代存活对象移入老年代需要ParNewGC和CMS的协同。这样的协同引入额外的代价,也就导致CMS的新生代GC暂停时间要略长。

第7列是GC暂停所耗费的时间占总时间的百分比,这个值可以很好地反映GC的总时间代价。因为并行GC总时间(最后一列)以及引入的CPU占用代价可以忽略。按前文所述,优化堆大小后老年代GC次数会变得很少,这样第7列的值主要由新生代GC暂停总时间所决定。新生代暂停总时间是新生代暂停(连续)时长(第3列)与暂停次数的乘积。新生代暂停频率与新生代空间大小有关,对传统回收器来说,这个大小是相同的(400MB)。因此,对传统回收器来说,第7列的值或多或少地反映着第3列的值(负载差不多的情况)。

CMS的優點可以從第6列明顯看出:它用稍長的總時間代價換來了更短(低一個量級)的老年代GC暫停。對許多真實環境的應用來說,這是一個不錯的折衷。

那麼,對於我們的應用,G1回收器表現怎麼樣呢?第6列(以及第5列)可以看出,在減少老年代GC暫停時長上,G1回收器比CMS回收器做的好。但從第7列也可以看到,它付出相當高的代價:同樣的負荷下,GC總時間代價佔7%,而CMS只佔1.6%。

我會在後續的文章中檢查在什麼條件下會導致G1產生更高的GC時間代價,同樣也會分析G1與其他回收器(尤其是CMS回收器)相比的優缺點。這是一個龐大且有價值的主題。

總結與展望

對所有的經典Java GC演算法(SerialGC、ParallelGC、ParNewGC和CMS)來說,優化各代堆空間大小是很重要的,然而實際中許多應用程序並沒有做足夠合理的優化。導致的結果就是應用效能不夠優化,以及操作退化(造成效能損失,如果沒有很好地監控甚至會出現一段時間內程式暫停的情況)。

優化各代堆空間大小可顯著提升應用效能,並將GC長暫停次數降至最低。然後,消除GC長暫停需要使用低延遲回收器。 CMS一直(直到現在)是首選且有效的低延遲回收器。在很多情況下,CMS就可以滿足需求。透過合理的優化,它還是可以確保長期穩定,只不過有堆碎片的風險。

作為替代,G1回收器目前(Java 7u9)是一個被支援且可用的選擇,但仍有改進的空間。對許多應用來說,它的結果可以接受,但與CMS回收器相比還不是很好。其優缺點的細節值得仔細研究

以上是詳解為任務關鍵型Java應用優化垃圾回收(下)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!