成爲Java GC專家系列(3) — 如何優化Java垃圾回收機制

英文原文:cubrid,編譯:ImportNew王曉傑 html

本文是成爲Java GC專家系列文章的第三篇。在第一篇《成爲JavaGC專家Part I — 深刻淺出Java垃圾回收機制》中咱們學習了不一樣GC算法的執行過程,GC是如何工做的,什麼是新生代和老年代,你應該瞭解的JDK7中的5種GC類型,以及這5種類型對於應用性能的影響。 java

在第二篇《成爲JavaGC專家Part II — 如何監控Java垃圾回收機制》,我解釋了JVM其實是如何執行垃圾回收的,咱們如何監控GC,以及那哪些具可讓咱們的工做更快,更高效。在第三篇文章中,咱們會基於實際的例子來解釋一些優化GC的最佳實踐。我認爲在閱讀本篇文章以前,你已經很好地理解了以前的文章,所以,爲了你可以更好地學習本文,若是你尚未讀過以前的兩篇文章話,請先閱讀。 算法

爲何須要優化GC 服務器

或者說的更確切一些,對於基於Java的服務,是否有必要優化GC應該說,對於全部的基於Java的服務,並不老是須要進行GC優化,但前提是所運行的基於Java的系統,包含了以下參數或行爲: 併發

  • 已經經過 -Xms 和–Xmx 設置了內存大小
  • 包含了 -server 參數
  • 系統中沒有超時日誌等錯誤日誌

換句話說,若是你沒有設定內存的大小,而且系統充斥着大量的超時日誌時,你就須要在你的系統中進行GC優化了。 oracle

可是,你須要時刻銘記一條GC優化永遠是最後一項任務。 jvm

想一下進行GC優化的最根本緣由,垃圾收集器清除在Java程序中建立的對象,GC執行的次數即須要被垃圾收集器清理的對象個數,與建立對象的數量成正比,所以,首先你應該減小建立對象的數量ide

俗話說的好,「冰凍三尺非一日之寒」。咱們應該從小事作起,不然日積月累就會很難管理。 工具

  • 咱們須要使用StringBuilder 或者StringBuffer 來替代String
  • 應該儘可能少的輸出日誌

可是,咱們知道有些狀況會讓咱們一籌莫展,咱們眼睜睜的看着XML以及JSON解析佔用了大量的內存。即使咱們已經儘量少的使用String以及儘可能少的輸出日誌,大量的臨時內存被用於XML或者JSON解析,例如10-100MB。可是,捨棄XML和JSON是很難的。咱們只要知道,他會佔用不少內存。 性能

若是應用內存使用量重複幾回調整以後增長了,你就能夠開始GC優化了。

我爲GC優化概括了兩個目的:

  1. 一個是將轉移到老年代的對象數量降到最少
  2. 另外一個是減小Full GC的執行時間

將轉移到老年代的對象數量降到最少

按代的GC機制由Oracle JVM提供,不包括能夠在JDK7以及更高版本中使用的G1 GC。換句話說,對象被建立在伊甸園空間,然後轉化到倖存者空間,最終剩餘的對象被送到老年代。某些比較大的對象會在被建立在伊甸園空間後,直接轉移到老年代空間。老年代空間上的GC處理會新生代花費更多的時間。所以,減小被移到老年代對象的數據能夠顯著地減小Full GC的頻率。減小被移到老年代空間的對象的數量,可能被誤解爲將對象留在新生代。可是,這是不可能的。取而代之,你能夠調整新生代空間的大小。

減小Full GC執行時間

Full GC的執行時間比Minor GC要長不少。所以,若是Full GC花費了太多的時間(超過1秒),一些鏈接的部分可能會發生超時錯誤。

  • 若是你試圖經過消減老年代空間來減小Full GC的執行時間,可能會致使OutOfMemoryError 或者 Full GC執行的次數會增長。
  • 與之相反,若是你試圖經過增長老年代空間來減小Full GC執行次數,執行時間會增長。

所以,你須要將老年代空間設定爲一個「合適」的值。

影響GC性能的參數

正如咱們在第二篇文章結尾提到的,不要幻想「某我的設定了GC參數後性能獲得極大的提升,咱們爲何不和他用同樣的參數?」,由於不一樣的Web服務所建立對象的大小和他們的生命週期都不盡相同。

簡單來講,若是一個任務的執行條件是A,B,C,D和E,一樣的任務執行條件換爲A和B,你會以爲哪一個更快?從通常人的直覺來看,在A和B條件下執行的任務會更快。

Java GC參數也是相同的道理,設定一些參數不但沒有提升GC執行速度,反而可能致使他更慢。GC優化的最基本原則是將不一樣的GC參數用於2臺或者多臺服務器,並進行對比,並將那些被證實提升了性能或者減小了GC執行時間的參數應用於服務器。請謹記這一點。

下面這個表格列出了GC參數中與內存大小相關的,能夠影響性能的參數。

1GC優化須要考慮的Java參數

定義

參數

描述

堆內存空間

-Xms

Heap area size when starting JVM

啓動JVM時的堆內存空間。

-Xmx

Maximum heap area size

堆內存最大限制

新生代空間

-XX:NewRatio

Ratio of New area and Old area

新生代和老年代的佔比

-XX:NewSize

New area size

新生代空間

-XX:SurvivorRatio

Ratio ofEdenarea and Survivor area

伊甸園空間和倖存者空間的佔比

我在進行GC優化時常用-Xms,-Xmx和-XX:NewRatio。-Xms和-Xmx是必須的。你如何設定NewRatio 會對GC性能產生十分顯著的影響。有些人可能會問如何設定Perm區域的大小?你能夠經過-XX:PermSize 和-XX:MaxPermSize參數來設定,

當OutOfMemoryError 錯誤發生而且是因爲Perm空間不足致使時,另外一個可能影響GC性能的參數是GC類型。下表列出了全部可選的GC類型(基於JDK6.0)

2GC類型可選參數

分類

參數

備考

Serial GC

-XX:+UseSerialGC

Parallel GC

-XX:+UseParallelGC
-XX:ParallelGCThreads=value

Parallel Compacting GC

-XX:+UseParallelOldGC

CMS GC

-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=value
-XX:+UseCMSInitiatingOccupancyOnly

G1

-XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC

在JDK6中這兩個參數必須同時使用

除了G1 GC,能夠經過每種類型第一行的參數來切換GC類型。最經常使用的GC類型是Serial GC。他專門針對客戶端系統進行了優化。

影響GC性能的參數有不少,可是上面提到的參數會帶來最顯著的效果。請牢記,設定過多的參數不必定會減小GC執行時間。

GC優化過程

GC優化的過程與大多數性能改善的過程及其相似。下面是我使用的GC優化過程。

1.監控GC狀態

首先你須要監控GC來檢查在系統執行過程當中GC的各類狀態。請參考前一篇文章中提到的監控方法 成爲JavaGC專家Part II — 如何監控Java垃圾回收機制

2.在分析監控結果後,決定是否進行GC優化

在檢查GC狀態的過程當中,你應該分析監控結果以便決定是否進行GC優化,若是分析結果代表執行GC的時間只有0.1-0.3秒,那你就不必浪費時間去進行GC優化。可是,若是GC的執行時間是1-3秒,或者超過10秒,GC將勢在必行。

可是,若是你已經爲Java分配了10GB的內存,而且不能再減小內存大小,你將沒法再對GC進行優化。在進行GC優化以前,你必須想清楚你爲何要分配如此大的內存空間。假如當你分1 GB 或 2 GB內存時出現OutOfMemoryError ,你應該執行堆內存轉儲(heap dump),並消除隱患。

注意:

堆內存轉儲是一個用來檢查Java內存中的對象和數據的文件。該文件能夠經過執行JDK中的jmap命令來建立。在建立文件的過程當中,Java程序會暫停,所以不要再系統執行過程當中建立該文件。

你能夠在互聯網上搜索堆內存[s1] 轉儲的詳細說明。對於韓國的讀者,能夠參考我去年發佈的書: The story of troubleshooting for Java developers and system operators (Sangmin Lee, Hanbit Media, 2011, 416 pages)。

3. 調整GC類型/內存空間

若是你已經決定要進行GC優化,那麼就要選擇GC類型和設定內存空間。在這時,若是你有幾臺不一樣服務器,請時刻牢記,檢查每一臺服務器的GC參數,並進行有針對性的優化。

4.分析結果

在調整了GC參數並持續收集24小時以後,開始對結果進行分析,若是你幸運的話,你就找到那些最適合系統的GC參數。反之,你須要經過分析日誌來檢查內存是如何被分配的。而後你須要經過不斷的調整GC類型和內存空間大小一邊找到最佳的參數。

5. 若是結果使人滿意,你能夠將該參數應用於全部的服務器,並中止GC優化

有過GC優化結果使人滿意,你能夠應用於全部的服務器,下面的章節中,咱們將看到每一個步驟的具體任務。

監控GC狀態及分析結果

查看運行中的Web Application Server (WAS)的GC狀態的最佳方法是經過jstat命令,在第二篇文章成爲JavaGC專家Part II — 如何監控Java垃圾回收機制中我已經詳細解釋過jstat命令,所以本篇文章我將重點描述數據部分。

下面這個例子展示了某個JVM在進行GC優化以前的狀態。

(很遺憾,這不是一個操做服務器)

1
2
3
4
$ jstat -gcutil 21719 1s
S0    S1    E    O    P    YGC    YGCT    FGC    FGCT GCT
48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673
48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673

如上表,咱們先看一下YGC 和YGCT,計算YGCT/ YGC獲得0.050秒(50毫秒)。這意味着新生代空間上的GC操做平均花費50毫秒。在這種狀況,你大可沒必要擔憂新生代空間上執行的GC操做。
接下來,咱們來看一下FGCT 和FGC。,計算FGCT/ FGC獲得19.68秒,這意味着GC的平均執行時間爲19.68秒,多是每次花費19.68秒執行了三次,也多是其中的兩次執行了1秒而另外一次執行了58秒。不論哪一種狀況,都須要進行GC優化。

經過jstat 命令能夠很輕易地查看GC狀態,可是,分析GC的最佳方式是經過–verbosegc參數來生成日誌,在以前的文章中我已經解釋瞭如何分析這些日誌,HPJMeter 是我我的最喜歡的用於分析-verbosegc 日誌的工具。他很易於使用和分析結果。經過HPJmeter你能夠很輕易查看GC執行時間以及GC發生頻率。若是GC執行時間知足下面全部的條件,就意味着無需進行GC優化了。

  • Minor GC執行的很快(小於50ms)
  • Minor GC執行的並不頻繁(大概10秒一次)
  • Full GC執行的很快(小於1s)
  • Full GC執行的並不頻繁(10分鐘一次)

上面提到的數字並非絕對的;他們根據服務狀態的不一樣而有所區別,某些服務可能知足於Full GC每次0.9秒的速度,但另外一些可能不是。所以,針對不一樣的服務設定不一樣的值以決定是否進行GC優化。

在查看GC狀態的時候有件事你須要特別注意,那就是不要只關注Minor GC 和Full GC的執行時間。還要關注GC執行的次數,例如,當新生代空間較小時,Minor GC會過於頻繁的執行(有時每秒超過1次)。另外,轉移到老年代的對象數增多,則會致使Full GC執行次數增多。所以,別忘了加上–gccapacity參數來查看具體佔用了多少空間。

設定GC類型/內存空間大小

  • 設定GC類型

OracleJVM有5種GC類型,可是在JDK7以前的版本中,只能在Parallel GC, Parallel Compacting GC 和CMS GC之中選擇一個,對於選擇哪一個沒有明確的原則和規則。

這樣的話,咱們該如何選擇呢?強烈建議三者都選,可是,有一點是很明確的:CMS GC比Parallel GCs更快。若是真的如此,那麼就選CMS GC了。可是,CMS GC也不老是更快。總體來看,CMS GC模式下的Full GC執行更快,不過,一旦出現並行模式失敗,他將比Parallel GC更慢。

併發模式失敗

咱們來詳細講解一下併發模式失敗。

Parallel GC 和 CMS GC 最大的不一樣來自於壓縮任務。壓縮任務是經過刪除已分配內存空間中的空白空間以便壓縮內存,清理內存碎片。

在Parallel GC模式下,壓縮工做在Full GC執行時進行,這會費不少時間,可是,在執行完Full GC以後,因爲可以順序地分配空間,隨後的內存可以被更快的分配。

與之相反的,CMS GC並不進行壓縮處理,所以,CMS GC執行的更快。可是,因爲沒有壓縮,在進行磁盤清理以前,內存中會有不少空白空間。這就是說,可能沒有足夠的空間存儲大的對象,例如,雖然老年代空間還有300MB空間,可是一些10MB的對象沒法被順序的存儲。在這種狀況下,會出現「並行模式失敗」警告,並執行壓縮處理。在CMS GC模式下,壓縮處理的執行時間要比Parallel GCs長不少。另外,這還將致使另一個問題。關於併發模式失敗的詳細說明,能夠參考Oracle工程師撰寫的Understanding CMS GC Logs

綜上所述,你須要找到最適合你的系統的GC類型。

每一個系統都有最適合他的GC類型等着你去尋找,若是你有6臺服務器。我建議你每兩臺設置相同的參數。並添加 –verbosegc參數,分析結果。

  • 設定內存空間大小

下表展現了內存空間大小,GC執行次數以及GC執行時間三者間的關係。

    • 大內存空間
      • 減少GC執行次數
      • 增長GC執行時間
    • 小內存空間
      • 減少GC執行時間
      • 增長GC執行次數

關於如何設置內存空間的大小,沒有惟一的標準答案。若是服務器資源足夠,並且Full GC也可能在1秒內完成,設置爲10GB固然可行。。但絕大多數服務器並非這樣,當內存設爲10GB時,可能要花費10~30秒來執行Full GC。固然,執行時間會隨對象的大小而改變。

鑑於如此,咱們應該如何設定內存空間大小呢?通常來講,我建議爲500MB。不過請注意這不是讓你將WAS的內存參數設置爲–Xms500m 和–Xmx500m。根據優化GC以前的狀態,若是Full GC執行以後內存空間剩餘300MB,那麼最好將內存設置爲1GB(300MB(默認程序佔用)+ 500MB(老年代最小空間)+200MB(空閒內存))。也就是說你要爲老年代額外設置500MB。所以,若是你有三個執行服務器,內存分別設置爲1GB,1.5GB,2GB,而且檢查結果。

理論上來說,GC執行速度應該遵循1GB> 1.5GB> 2GB,所以1GB執行GC速度最快。可是並不說明1GB空間的Full GC會花費1秒而2GB空間會花費2秒。時間取決於服務器的性能和對象的大小。所以,最佳的方式是創建儘量多的衡量指標來監控他們。

對於內存空間大小,你應該額外設定NewRatio參數。NewRatio參數是新生代和老年代空間的比例,即XX:NewRatio=1意味着新生代與老年代之比爲1:1。對於1GB來講就是新生代和老年代各500MB。若是NewRatio爲2,意味着新生代老年代之比爲1:2,所以該值越大,老年代空間越大,新生代空間越小。

這看似一件不是很重要的事情,但NewRatio參數會顯著地影響整個GC的性能。若是新生代空間很小,會用更多的對象被轉移到老年代空間,這樣致使頻繁的Full GC,增長暫停時間。

你能夠簡單的認爲NewRatio 爲1是最佳的選擇,可是,有時可能設置爲2或3更好,我就見過不少這樣的例子。

如何最快的完成GC優化?對比性能測試的結果應該是最快地方法,爲每一臺服務器設置不一樣的參數並監控他們的狀態,強烈建議至少監控1或2天的數據。可是,當你對GC優化是,你要確保每次執行相同的負載。而且請求的比率,例如URL都應該是一致的。不過,即使對於專業測試人員要想精確的控制負載也是很難的,並要花費大量的時間準備。所以,相對來講比較方便和容易的方法是調整才參數,以後花費較長的時間收集結果。

分析GC優化結果

在設置了GC參數以及-verbosegc參數以後,經過tail命令確保日誌被正確的生成。若是參數設置的不正確或者日誌沒有生成,你將白白浪費你的時間。若是日誌正確的話,持續收集1到2天。隨後最好將日誌下載到本地PC並用HPJMeter來分析

  • Full GC 執行時間
  • Minor GC執行時間
  • Full GC 執行間隔
  • Minor GC 執行間隔
  • Entire Full GC 執行時間
  • Entire Minor GC 執行時間
  • Entire GC 執行時間
  • Full GC e執行時間
  • Minor GC 執行時間

找到最佳的GC參數是件很是幸運的事情,然而在大多數場合,咱們並不會獲得幸運之神的眷顧,在進行GC優化時要儘可能當心謹慎,想一步完成優化每每會致使OutOfMemoryError 。

優化示例

好了,咱們一直在紙上談兵,如今咱們看一些實際的GC優化的例子。

示例1

下面這個例子針對 Service S的優化,對於最近被部署的 Service S,Full GC花費了太長的時間。

請看 jstat –gcutil的執行結果。

1
2
S0 S1 E O P YGC YGCT FGC FGCT GCT
12.160.005.1863.7820.32542.04756.9468.993

最左邊的Perm 空間對於最初的GC優化不是很重要,這一次YGC參數的值更加有用。

Minor GC和Full GC的平均值以下表所示

3Service SMinor GC Full GC的平均執行時間

GC 類型

GC 執行次數

GC 執行時間

平均

Minor GC

54

2.047

37 ms

Full GC

5

6.946

1,389 s

最重要的是下面兩個數據

  • 新生代實際使用空間: 212,992 KB
  • 老年代實際使用空間: 1,884,160 KB

所以,總的內存空間爲2GB,不算Perm空間的話,新生代與老年代之比爲1:9。經過jstat和-verbosegc 日誌進行數據收集,並把三臺服務器按照以下方式設置。

  • NewRatio=2
  • NewRatio=3
  • NewRatio=4

一天以後,檢查系統的GC日誌後發現,在設置了NewRatio參數後很幸運的沒有發生Full GC,

爲何?

  • NewRatio=2: 45 ms
  • NewRatio=3: 34 ms
  • NewRatio=4: 30 ms

咱們看到NewRatio=4 是最佳的參數,雖然它的新生代空間最小,但GC時間確最短。設定這個參數以後,系統沒有執行過Full GC。

爲了說明這個問題,下面是服務之星一段時間後執行jstat –gcutil的結果

1
2
S0 S1 E O P YGC YGCT FGC FGCT GCT
8.610.0030.6724.6222.38242430.21900.00030.219

你可能會認爲由於服務器接受的請求少才致使的GC執行頻率降低。實際上,雖然Full GC沒有執行,可是Minor GC被執行了 2,424次。

示例2

這是一個針對ServiceA的例子,咱們經過公司內部的應用性能管理系統(APM)發現JVM暫停了至關長的時間(超過8秒),所以咱們進行了GC優化。咱們找到了Full GC執行時間過長的緣由,並着手解決。

進行GC優化的第一步,就是咱們添加了-verbosegc參數,並獲得以下結果。

1:進行GC優化以前的STW時間

如上圖所示,由HPJMeter自動生成的圖片之一。X座標表示JVM執行的時間。Y座標表示每次GC的時間。CMS綠點,表示Full GC結果。Parallel Scavenge藍點,表示Minor GC結果。

以前我曾經說過CMS GC是最快的,可是上面的的結果顯示出於某種緣由,它最多花費了15秒。是什麼致使這個結果?是否想起我以前提過的,CMS在進行內存清理時,會變慢。與此同時,服務的內存被設定爲 –Xms1g和–Xmx4g ,且實際分配了4GB內存。

所以,我將GC類型從CMS改成Parallel GC。而且將內存改成2GB,設定NewRatio 爲3。幾小時以後我使用 jstat –gcutil獲得以下結果

1
2
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.0030.483.3126.5437.0122611.131411.75822.890

相對於4GB時的15秒,Full GC變成了平均每次3秒。可是3秒同樣比較慢,所以我設計了以下6種場景。

  • Case 1: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=2
  • Case 2: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=3
  • Case 3: -XX:+UseParallelGC -Xms1g -Xmx1g -XX:NewRatio=3
  • Case 4: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=2
  • Case 5: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=3
  • Case 6: -XX:+UseParallelOldGC -Xms1g -Xmx1g -XX:NewRatio=3

那一個最快呢?結果顯示,內存越小,結果越好。下圖展現了Case6的結果。這是GC的性能最好。最長的響應時間只有1.7秒。平均時間在1秒以內。

2Case6的時間圖表

基於以上結果。咱們按照Case6調整了GC參數。可是,這致使了天天晚上都會發生OutOfMemoryError。在這裏很難解釋具體的緣由。簡單來講,批處理程序致使了內存泄漏。相關的問題已經被解決。

若是對GC日誌只分析很短的時間就貿然對全部服務器進行優化是很是危險的。請時刻牢記,你必須同時分析GC日誌和應用程序。

咱們回顧了兩個關於GC優化的例子,正如我以前提到的,例子中提到的GC參數,能夠設置在相同的服務器之上,但前提是他們具備相同的CPU,操做系統,JDK版本以及運行着相同的服務。可是不要直接把我用過的參數用到你的服務至上,它們未必能很好的工做。

結論

我憑藉經驗進行GC優化,而沒有執行堆轉儲並分析內存的詳細內容。精確地分析內存能夠獲得更好的優化效果。可是,這種分析通常適用於內存使用量相對固定的場合。不過,若是服務嚴重過載並佔用的大量的內存,強力建議根據以前的經驗進行GC優化。

我已經在一些服務上設置了G1 GC參數,並進行過性能測試。但尚未應用與正式環境,G1 GC參數的速度要快於其餘任何GC類型。可是,你必需要升級到JDK7。另外,他的穩定性也暫時沒有保障,沒人知道是否會出現致命的錯誤。所以還不到將其正式應用的時候

在將來的某一天,等到JDK7真正穩定了(這不是說他如今不穩定),而且WAS針對JDK7進行優化後,G1 GC最終可以按照預期的那樣工做了,咱們可能就不須要在進行GC優化了。

想了解GC優化的更多內容,請登陸Slideshare.com 查看關聯資源。強烈推薦Everything I Ever Learned About JVM Performance Tuning @Twitter 。做者Attila Szegedi,一位Twitter工程師。請花些時間閱讀。

By Sangmin Lee, NHN Performance Engineering Lab.

做者Sangmin Lee,就任於NHN性能工程研究院

英文原文:cubrid,編譯:ImportNew-王曉傑

譯文地址: http://www.importnew.com/3146.html

相關文章
相關標籤/搜索