GC調優入門筆記

想給項目代碼作作調優但有許多疑惑,好比有哪些參數要調、怎麼調、使用什麼工具、調優的效果如何定量測量等。發現Oracle的這份資料不錯,簡潔直接,回答了個人許多問題,給了許多很實用的大方向上的指導。將其中精華記錄下來,但願能給一樣入門的朋友一些啓示。html

Garbage Collectors

垃圾收集器 (Garbage Collectors)是JVM中的內存管理工具。它的職責包括:java

  • 在年輕代爲對象分配空間,並將存活比較久的對象移動到年老代;
  • 在堆佔用率超過某閾值時觸發concurrent marking phase,在年老代找到存活的對象;
  • 觸發parallel copying,壓縮活着的對象,釋放垃圾空間。

看起來有點抽象,而且貌似沒提到年輕代的垃圾收集,其實已經在第一條中提到了:「將存活比較久的對象移動到年老代」,這裏隱含了對年輕代進行存活對象登記和收集的過程。簡而言之,垃圾收集器的職責是:給對象分配內存;收集年輕代垃圾;收集年老代垃圾程序員

串/並行Garbage Collector的選擇

通常來講,JVM會根據系統的物理配置等因素選擇一個默認的垃圾處理器。但顯然,不一樣的應用程序有不一樣的行爲(如使用內存的頻率、對象的平均存活時間);也有不一樣的要求(若有界面的程序要求響應快速,而服務器端程序要求吞吐量高,能處理更多的請求)。因此,根據不一樣程序的特色,可能須要不一樣的垃圾處理器來管理。在此處,咱們先從串/並行的角度淺淺地瞭解一下這個問題。web

垃圾處理器能夠粗略地分爲串行進行和並行進行的,即垃圾處理這個過程在單線程仍是多線程中進行;在Java SE 1.4以前的版本不支持並行。根據Amdahl's law (程序可以經過並行來加速的程度取決於程序中必須串行運行的部分),若是GC是串行進行的,則一個並行的應用程序的加速程度會受到GC的影響。假設咱們經過增長處理器個數的方式來加速一個應用程序,那麼隨着處理器個數的增多,GC拖後腿的程度也愈來愈厲害,看下圖:服務器

GC時間所佔的百分比隨處理器個數的變化多線程

這是一個數學模擬圖,模擬了一個理想(徹底並行)的應用程序的吞吐量受GC時間的影響。橫軸表明處理器個數,縱軸表明吞吐量,不一樣顏色的曲線表明GC百分比不一樣的程序。紅色曲線表示一條在單CPU下GC時間佔1%的程序,在處理器個數增長到32個時,GC佔整個程序運行時間的百分比竟超過了20%。oracle

能夠看到GC所佔的時間百分比越大,拖後腿的程度就越厲害。這是一個很好理解的現象,由於GC是串行的,因此其運行時間不受處理器數量的影響。隨着處理器的增多,應用程序自己的運行時間降低了,因此顯得GC所佔的時間百分比愈來愈大。分佈式

所以,在小系統上開發應用時能夠忽略的一些GC小問題,當擴展到大型系統上時就會變得十分可觀,甚至成爲性能瓶頸。可是,此時在垃圾處理器上作一些小文章就有可能極大地增長性能。好比考慮到上圖反映的現象,或許咱們能夠考慮換一個並行的垃圾處理器以提升吞吐量。ide

另外一方面,小型應用若是不須要其餘特殊的GC行爲,一般使用串行垃圾處理器就夠了,選擇其餘垃圾處理器可能反而會引入額外的複雜性和開銷。工具

分代模型

在處理垃圾時,須要先找到全部活着的對象,而後將剩下的做爲垃圾進行處理。「找到全部活着的對象」這個過程須要耗費的時間與活着的對象數量成正比,這樣的話,若是應用中原本就維護了大量的存活對象,那麼找到活着的對象須要耗費大量的時間。爲了優化這個過程,JVM程序員們基於一些經驗提出了分代收集的思想。在這些經驗中,最重要的是分代假設,即大部分對象都只存活很短的時間。

對象壽命的典型分佈圖

上圖中,橫軸表明總的字節分配數,即時間軸;縱軸表明不一樣時間下存活的對象所佔字節數。左邊的尖峯表明分配空間沒多久就能夠回收的對象,好比在某個loop中臨時分配的對象,它們的壽命只有一個loop的時間;最右邊表明存活好久的那些對象,好比初始化時就存在且一直活到程序結束的對象;在這兩極之間,有一些用於中間計算的對象,即左邊的尖峯右邊的這個包。

不一樣應用程序的對象壽命分佈圖是不同的,可是許多應用的大體分佈都符合上面這個圖,這爲分代收集奠基了一個很好的事實基礎:大部分對象都在年輕時死去。

好比在一個公園裏掃落葉,騰出空地讓行人行走。有一些樹掉葉子特別厲害,一小會兒地上就掉滿了;而另一些樹每小時只掉一兩片葉子。假設清潔工爲了省力,每過一段時間就清掃一次,掃完了則回到椅子上休息。若是我是清潔工,確定會選擇集中打掃那些掉得厲害的樹,並且可能會以比較高的頻率打掃;至於那些掉得不厲害的樹,只要偶爾看一下,等落滿的時候再打掃就行了。若是每次都要把全部的樹下打掃一遍,爲了照顧那些掉得厲害的樹個人打掃頻率須要很高,我會很累,並且打掃時間也會變長,效率下降。

除了串行收集器和G1以外,其餘收集器默認使用如下分代模型:

默認分代模型

模型分爲年輕代和年老代,年輕代分爲eden和兩個survivor,virtual空間表明JVM向操做系統預訂但還未實際分配的空間。

調優指標

maximum pause time

pause time是指垃圾處理器中止應用程序的運行,專一於空間釋放時所花的時間。若是使用的是並行垃圾處理器,能夠通過-XX:MaxGCPauseMillis=<nnn>這個命令行參數設按期望的最大pause time。(若是未設置,默認沒有最大時間要求)

垃圾處理器會維護每次垃圾收集pause time的均值和方差,當均值與方差的和大於設置的MaxGCPauseMillis參數時,垃圾處理器會認爲停留時間目標未達到,而後調整堆的大小和其它的有關參數來試圖達到目標。

此處的maximum pause time和下面即將提到的throughput是一對相愛相殺的姐妹。一般減少堆size會優化pause time(掃描、處理時間減小),可是堆變小形成GC頻率升高,從而致使throughput降低。對於這二者,垃圾處理器的處理方式是優先達到設置的pause time目標,其次再達到throughput目標。

throughput

吞吐量經過GC時間比例測量。 GC時間比例 = GC時間 / (GC時間 + 應用運行時間),其中的GC時間包括全部代的GC時間。若是使用的是並行垃圾處理器,吞吐量能夠經過-XX:GCTimeRatio=<nnn>設置,若<nnn>爲19,則GC時間比例爲1/(1+19)=5%,即垃圾處理的時間佔總時間的5%。

若是GCTimeRatio未達到要求,垃圾收集器會增長年輕代和年老代的大小來下降GCTimeRatio

footprint

內存佔用(memory footprint)指程序運行時佔用和引用的內存大小。

若是前面兩個目標達到了,垃圾處理器會自動收縮堆,直至其中一個目標再也不知足(必定是throughput,由於堆變小會使停留時間變短),而後再試圖知足這個目標。

promptness

及時性 (promptness)定義爲對象死去以後到對象所佔用的內存可使用以前的時間。這個指標對分佈式系統一般比較重要。

通常調優策略

  1. 若是堆已經達到maximum heap size但throughput目標還未達到,說明設置的maximum heap size過小,能夠嘗試將其設爲接近物理內存但還不至於致使內存交換的值。若是仍是達不到throughput目標,說明這個throughput目標對於當前平臺上的內存大小來講太高了。
  2. 若是throughput目標已經知足,但停留時間過長,則能夠增長maximum pause time目標。但這樣throughput目標有可能又得不到知足了,此時須要根據本身的判斷做一個折中。
  3. 如上文所說,throughput與pause time對堆大小的要求相反,是一對相互競爭的指標。它們之間的相互競爭可能形成的結果是:即便應用程序已經在穩定運行了,堆大小仍然在上下振動。這代表垃圾處理器努力在二者之間尋找一個平衡。

度量

以上所說的throughput等指標須要根據應用的不一樣特性去測量。好比要測一個web server的throughput,能夠用一個本身寫的client load generator;測試Solaris系統上服務器的內存佔用,能夠用pmap這個命令;若要測GC的停留時間,則能夠經過命令行參數-verbose:gc直接觀察JVM的診斷輸出。

總結

本文定性介紹了GC調優的一些初級概念,爲實際調優奠基基礎。但僅有這些模糊概念是遠遠不夠的,在理論和實踐上還會做出其它總結,歡迎關注。

參考資料/推薦閱讀

Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide 文中提到的資料,Oracle寫的,具備官方指導意義

深刻理解Java虛擬機 推薦看第4-5章,詳細講解了調優工具的使用以及幾個調優實例

高質量Java程序設計 推薦看第3章,做者用一個xml parser的例子給出了調優實戰講解,惋惜本書再也不再版,也未找到電子版

相關文章
相關標籤/搜索