jvm參數優化

1、HotSpot JVM 提供了三類參數

如今的JVM運行Java程序(和其它的兼容性語言)時在高效性和穩定性方面作的很是出色。例如:自適應內存管理、垃圾收集、及時編譯、動態類加載、鎖優化等。雖然有了這種程度的自動化(或者說有這麼多自動化),可是JVM仍然提供了足夠多的外部監控和手動調優工具(容許命令行參數能夠在JVM啓動時傳入到JVM中)。在有錯誤或低性能的狀況下,JVM必須可以讓調試,JVM提供了幾百個這樣的參數,因此若是沒有這方面的知識很容易迷失。java

1)第一類包括了標準參數。顧名思義,標準參數中包括功能和輸出的參數都是很穩定的,極可能在未來的JVM版本中不會改變。你能夠用java命令(或者是用 java -help)檢索出全部標準參數。例如:-server。web

2)第二類是X參數,非標準化的參數在未來的版本中可能會改變。全部的這類參數都以-X開始,而且能夠用java -X來檢索。注意,不能保證全部參數均可以被檢索出來,其中就沒有-Xcomp。算法

3)第三類是包含XX參數(到目前爲止最多的),它們一樣不是標準的,甚至很長一段時間內不被列出來。然而,在實際狀況中X參數和XX參數並無什麼不一樣。X參數的功能是十分穩定的,然而不少XX參數仍在實驗當中(主要是JVM的開發者用於debugging和調優JVM自身的實現)。值的一讀的介紹非標準參數的文檔 HotSpot JVM documentation,其中明確的指出XX參數不該該在不瞭解的狀況下使用。這是真的,而且我認爲這個建議一樣適用於X參數(一樣一些標準參數也是)。無論類別是什麼,在使用參數以前應該先了解它可能產生的影響。
用一句話來講明XX參數的語法。全部的XX參數都以」-XX:」開始,可是隨後的語法不一樣,取決於參數的類型。緩存

  • 對於布爾類型的參數,咱們有」+」或」-「,而後才設置JVM選項的實際名稱。例如,-XX:+用於激活選項,而-XX:-用於註銷選項。
  • 對於須要非布爾值的參數,如string或者integer,咱們先寫參數的名稱,後面加上」=」,最後賦值。例如, -XX:=給賦值。

2、標準參數:

一、-server and -client
有兩種類型的 HotSpot JVM,即」server」和」client」。服務端的VM中的默認爲堆提供了一個更大的空間以及一個並行的垃圾收集器,而且在運行時能夠更大程度地優化代碼。客戶端的VM更加保守一些(校對注:這裏做者指客戶端虛擬機有較小的默認堆大小),這樣能夠縮短JVM的啓動時間和佔用更少的內存。有一個叫」JVM功效學」的概念,它會在JVM啓動的時候根據可用的硬件和操做系統來自動的選擇JVM的類型。具體的標準能夠在這裏找到。從標準表中,咱們能夠看到客戶端的VM只在32位系統中可用。
注:雖然當初服務端VM的目標是長時間運行的服務進程,可是如今看來,在運行獨立應用程序時它比客戶端VM有更出色的性能安全

二、-version and -showversion服務器

當咱們調用「java」命令時,咱們如何才能知道咱們安裝的是哪一個版本的Java和JVM類型呢?在同一個系統中安裝多個Java,若是不注意的話有運行錯誤JVM的風險。在不一樣的Linux版本上預裝JVM這方面,我認可如今已經變的比之前好不少了。幸運的是,咱們如今能夠使用-version參數,它能夠打印出正在使用的JVM的信息。多線程

-version參數在打印完上述信息後當即終止JVM。還有一個相似的參數-showversion能夠用來輸出相同的信息,可是-showversion緊接着會處理並執行Java程序。所以,-showversion對幾乎全部Java應用的命令行都是一個有效的補充。你永遠不知道你何時,忽然須要瞭解一個特定的Java應用(崩潰時)使用的JVM的一些信息。在啓動時添加-showversion,咱們就能保證當咱們須要時能夠獲得這些信息。併發

三、-Xint, -Xcomp, 和 -Xmixed
-Xint和-Xcomp參數和咱們的平常工做不是很相關,可是我很是有興趣經過它來了解下JVM。在解釋模式(interpreted mode)下,-Xint標記會強制JVM執行全部的字節碼,固然這會下降運行速度,一般低10倍或更多。-Xcomp參數與它(-Xint)正好相反,JVM在第一次使用時會把全部的字節碼編譯成本地代碼,從而帶來最大程度的優化。這聽起來不錯,由於這徹底繞開了緩慢的解釋器。然而,不少應用在使用-Xcomp也會有一些性能損失,固然這比使用-Xint損失的少,緣由是-xcomp沒有讓JVM啓用JIT編譯器的所有功能。JIT編譯器在運行時建立方法使用文件,而後一步一步的優化每個方法,有時候會主動的優化應用的行爲。這些優化技術,好比,積極的分支預測(optimistic branch prediction),若是不先分析應用就不能有效的使用。另外一方面方法只有證實它們與此相關時纔會被編譯,也就是,在應用中構建某種熱點。被調用不多(甚至只有一次)的方法在解釋模式下會繼續執行,從而減小編譯和優化成本。app

注:混合模式也有他本身的參數,-Xmixed。最新版本的HotSpot的默認模式是混合模式,因此咱們不須要特別指定這個標記。咱們來用對象填充HashMap而後檢索它的結果作一個簡單的用例。每個例子,它的運行時間都是不少次運行的平均時間jvm

3、內存相關從參數優化:

理想的狀況下,一個Java程序使用JVM的默認設置也能夠運行得很好,因此通常來講,沒有必要設置任何JVM參數。然而,因爲一些性能問題(很不幸的是,這些問題常常出現),一些相關的JVM參數知識會是咱們工做中得好夥伴。

全部已制定的HotSpot內存管理和垃圾回收算法都基於一個相同的堆內存劃分:新生代(young generation)裏存儲着新分配的和較年輕的對象,老年代(old generation)裏存儲着長壽的對象。在此以外,永久代(permanent generation)存儲着那些須要伴隨整個JVM生命週期的對象,好比,已加載的對象的類定義或者String對象內部Cache。

一、-Xms and -Xmx (or: -XX:InitialHeapSize and -XX:MaxHeapSize)
-Xms和-Xmx能夠說是最流行的JVM參數,它們能夠容許咱們指定JVM的初始和最大堆內存大小。通常來講,這兩個參數的數值單位是Byte,但同時它們也支持使用速記符號,好比「k」或者「K」表明「kilo」,「m」或者「M」表明「mega」,「g」或者「G」表明「giga」。舉個例子,下面的命令啓動了一個初始化堆內存爲128M,最大堆內存爲2G,名叫「MyApp」的Java應用程序。java -Xms128m -Xmx2g MyApp

在實際使用過程當中,初始化堆內存的大小一般被視爲堆內存大小的下界。然而JVM能夠在運行時動態的調整堆內存的大小,因此理論上來講咱們有可能會看到堆內存的大小小於初始化堆內存的大小。但一般咱們能夠經過將「-Xms」和「-Xmx」設置爲相同大小來得到一個固定大小的堆內存。

注:-Xms和-Xmx其實是-XX:InitialHeapSize和-XX:MaxHeapSize的縮寫。咱們也能夠直接使用這兩個參數,它們所起得效果是同樣的:
$ java -XX:InitialHeapSize=128m -XX:MaxHeapSize=2g MyApp

注:全部JVM關於初始\最大堆內存大小的輸出都是使用它們的完整名稱:「InitialHeapSize」和「InitialHeapSize」。因此當你查詢一個正在運行的JVM的堆內存大小時,如使用-XX:+PrintCommandLineFlags參數或者經過JMX查詢,你應該尋找「InitialHeapSize」和「InitialHeapSize」標誌而不是「Xms」和「Xmx」。

二、-XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath
若是咱們無法爲-Xmx(最大堆內存)設置一個合適的大小,那麼就有可能面臨內存溢出(OutOfMemoryError)的風險,這多是咱們使用JVM時面臨的最可怕的猛獸之一。一般來講,分析堆內存快照(Heap Dump)是一個很好的定位手段,若是發生內存溢出時沒有生成內存快照那就實在是太糟了,幸運的是,咱們能夠經過設置-XX:+HeapDumpOnOutOfMemoryError 讓JVM在發生內存溢出時自動的生成堆內存快照。默認狀況下,堆內存快照會保存在JVM的啓動目錄下名爲java_pid.hprof 的文件裏(在這裏就是JVM進程的進程號)。也能夠經過設置-XX:HeapDumpPath=來改變默認的堆內存快照生成路徑,能夠是相對或者絕對路徑。

注:堆內存快照文件可能很龐大,特別是當內存溢出錯誤發生的時候。所以,咱們推薦將堆內存快照生成路徑指定到一個擁有足夠磁盤空間的地方。

三、-XX:OnOutOfMemoryError
當內存溢發生時,咱們甚至能夠能夠執行一些指令,好比發個E-mail通知管理員或者執行一些清理工做。經過-XX:OnOutOfMemoryError 這個參數咱們能夠作到這一點,這個參數能夠接受一串指令和它們的參數。在下面的例子中,當內存溢出錯誤發生的時候,咱們會將堆內存快照寫到/tmp/heapdump.hprof 文件而且在JVM的運行目錄執行腳本cleanup.sh.
$ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError =」sh ~/cleanup.sh」 MyApp

四、 -XX:PermSize and -XX:MaxPermSize
永久代在堆內存中是一塊獨立的區域,它包含了全部JVM加載的類的對象表示。爲了成功運行應用程序,JVM會加載不少類(由於它們依賴於大量的第三方庫,而這又依賴於更多的庫而且須要從裏面將類加載進來)這就須要增長永久代的大小。咱們能夠使用-XX:PermSize 和-XX:MaxPermSize 來達到這個目的。其中-XX:MaxPermSize 用於設置永久代大小的最大值,-XX:PermSize 用於設置永久代初始大小。下面是一個簡單的例子:
$ java -XX:PermSize=128m -XX:MaxPermSize=256m MyApp
:這裏設置的永久代大小並不會被包括在使用參數-XX:MaxHeapSize 設置的堆內存大小中。也就是說,經過-XX:MaxPermSize設置的永久代內存可能會須要由參數-XX:MaxHeapSize 設置的堆內存之外的更多的一些堆內存。

五、-XX:InitialCodeCacheSize and -XX:ReservedCodeCacheSize
JVM一個有趣的,但每每被忽視的內存區域是「代碼緩存」,它是用來存儲已編譯方法生成的本地代碼。代碼緩存確實不多引發性能問題,可是一旦發生其影響多是毀滅性的。若是代碼緩存被佔滿,JVM會打印出一條警告消息,並切換到interpreted-only 模式:JIT編譯器被停用,字節碼將再也不會被編譯成機器碼。所以,應用程序將繼續運行,但運行速度會下降一個數量級,直到有人注意到這個問題。就像其餘內存區域同樣,咱們能夠自定義代碼緩存的大小。相關的參數是-XX:InitialCodeCacheSize 和-XX:ReservedCodeCacheSize,它們的參數和上面介紹的參數同樣,都是字節值。

六、-XX:+UseCodeCacheFlushing

若是代碼緩存不斷增加,例如,由於熱部署引發的內存泄漏,那麼提升代碼的緩存大小隻會延緩其發生溢出。爲了不這種狀況的發生,咱們能夠嘗試一個有趣的新參數:當代碼緩存被填滿時讓JVM放棄一些編譯代碼。經過使用-XX:+UseCodeCacheFlushing 這個參數,咱們至少能夠避免當代碼緩存被填滿的時候JVM切換到interpreted-only 模式。不過,我仍建議儘快解決代碼緩存問題發生的根本緣由,如找出內存泄漏並修復它。

4、新生代垃圾回收參數優化:

單純從JVM的功能考慮,並不須要新生代,徹底能夠針對整個堆進行操做。新生代存在的惟一理由是優化垃圾回收(GC)的性能。更具體說,把堆劃分爲新生代和老年代有2個好處:簡化了新對象的分配(只在新生代分配內存),能夠更有效的清除再也不須要的對象(即死對象)(新生代和老年代使用不一樣的GC算法)

經過普遍研究面向對象實現的應用,發現一個共同特色:不少對象的生存時間都很短。同時研究發現,新生對象不多引用生存時間長的對象。結合這2個特色,很明顯 GC 會頻繁訪問新生對象,例如在堆中一個單獨的區域,稱之爲新生代。在新生代中,GC能夠快速標記回收」死對象」,而不須要掃描整個Heap中的存活一段時間的」老對象」。
SUN/Oracle 的HotSpot JVM 又把新生代進一步劃分爲3個區域:一個相對大點的區域,稱爲」伊甸園區(Eden)」;兩個相對小點的區域稱爲」From 倖存區(survivor)」和」To 倖存區(survivor)」。按照規定,新對象會首先分配在 Eden 中(若是新對象過大,會直接分配在老年代中)。在GC中,Eden 中的對象會被移動到survivor中,直至對象知足必定的年紀(定義爲熬過GC的次數),會被移動到老年代。

基於大多數新生對象都會在GC中被收回的假設,新生代的GC 使用複製算法。在GC前To 倖存區(survivor)保持清空,對象保存在 Eden 和 From 倖存區(survivor)中,GC運行時,Eden中的倖存對象被複制到 To 倖存區(survivor)。針對 From 倖存區(survivor)中的倖存對象,會考慮對象年齡,若是年齡沒達到閥值(tenuring threshold),對象會被複制到To 倖存區(survivor)。若是達到閥值對象被複制到老年代。複製階段完成後,Eden 和From 倖存區中只保存死對象,能夠視爲清空。若是在複製過程當中To 倖存區被填滿了,剩餘的對象會被複制到老年代中。最後 From 倖存區和 To倖存區會調換下名字,在下次GC時,To 倖存區會成爲From 倖存區。

總結一下,對象通常出生在Eden區,年輕代GC過程當中,對象在2個倖存區之間移動,若是對象存活到適當的年齡,會被移動到老年代。當對象在老年代死亡時,就須要更高級別的GC,更重量級的GC算法(複製算法不適用於老年代,由於沒有多餘的空間用於複製)

如今應該能理解爲何新生代大小很是重要了(譯者,有另一種說法:新生代大小並不重要,影響GC的因素主要是倖存對象的數量),若是新生代太小,會致使新生對象很快就晉升到老年代中,在老年代中對象很難被回收。若是新生代過大,會發生過多的複製過程。咱們須要找到一個合適大小,不幸的是,要想得到一個合適的大小,只能經過不斷的測試調優。這就須要JVM參數了

一、-XX:NewSize and -XX:MaxNewSize
就像能夠經過參數(-Xms and -Xmx) 指定堆大小同樣,能夠經過參數指定新生代大小。設置 XX:MaxNewSize 參數時,應該考慮到新生代只是整個堆的一部分,新生代設置的越大,老年代區域就會減小。通常不容許新生代比老年代還大,由於要考慮GC時最壞狀況,全部對象都晉升到老年代。(譯者:會發生OOM錯誤) -XX:MaxNewSize 最大能夠設置爲-Xmx/2 .

考慮性能,通常會經過參數 -XX:NewSize 設置新生代初始大小。若是知道新生代初始分配的對象大小(通過監控) ,這樣設置會有幫助,能夠節省新生代自動擴展的消耗。

二、-XX:NewRatio
能夠設置新生代和老年代的相對大小。這種方式的優勢是新生代大小會隨着整個堆大小動態擴展。參數 -XX:NewRatio 設置老年代與新生代的比例。例如 -XX:NewRatio=3 指定老年代/新生代爲3/1. 老年代佔堆大小的 3/4 ,新生代佔 1/4 .

若是針對新生代,同時定義絕對值和相對值,絕對值將起做用。下面例子:
$ java -XX:NewSize=32m -XX:MaxNewSize=512m -XX:NewRatio=3 MyApp
以上設置, JVM 會嘗試爲新生代分配四分之一的堆大小,但不會小於32MB或大於521MB

在設置新生代大小問題上,使用絕對值仍是相對值,不存在通用準則 。若是瞭解應用的內存使用狀況,設置固定大小的堆和新生代更有利,固然也能夠設置相對值。若是對應用的內存使用一無所知,正確的作法是不要設置任何參數,若是應用運行良好。很好,咱們不用作任何額外動做.若是遇到性能或OutOfMemoryErrors, 在調優以前,首先須要進行一系列有目的的監控測試,縮小問題的根源。

三、-XX:SurvivorRatio
參數 -XX:SurvivorRatio 與 -XX:NewRatio 相似,做用於新生代內部區域。-XX:SurvivorRatio 指定伊甸園區(Eden)與倖存區大小比例. 例如, -XX:SurvivorRatio=10 表示伊甸園區(Eden)是 倖存區To 大小的10倍(也是倖存區From的10倍).因此,伊甸園區(Eden)佔新生代大小的10/12, 倖存區From和倖存區To 每一個佔新生代的1/12 .注意,兩個倖存區永遠是同樣大的..

設定倖存區大小有什麼做用? 假設倖存區相對伊甸園區(Eden)過小, 相應新生對象的伊甸園區(Eden)永遠很大空間, 咱們固然但願,若是這些對象在GC時所有被回收,伊甸園區(Eden)被清空,一切正常.然而,若是有一部分對象在GC中倖存下來, 倖存區只有不多空間容納這些對象.結果大部分倖存對象在一次GC後,就會被轉移到老年代 ,這並非咱們但願的.考慮相反狀況, 假設倖存區相對伊甸園區(Eden)太大,固然有足夠的空間,容納GC後的倖存對象. 可是太小的伊甸園區(Eden),意味着空間將越快耗盡,增長新生代GC次數,這是不可接受的。

總之,咱們但願最小化短命對象晉升到老年代的數量,同時也但願最小化新生代GC 的次數和持續時間.咱們須要找到針對當前應用的折中方案, 尋找適合方案的起點是 瞭解當前應用中對象的年齡分佈狀況。

四、-XX:+PrintTenuringDistribution
參數 -XX:+PrintTenuringDistribution 指定JVM 在每次新生代GC時,輸出倖存區中對象的年齡分佈。例如:
Desired survivor size 75497472 bytes, new threshold 15 (max 15)

  • age 1: 19321624 bytes, 19321624 total
  • age 2: 79376 bytes, 19401000 total
  • age 3: 2904256 bytes, 22305256 total

第一行說明倖存區To大小爲 75 MB. 也有關於老年代閥值(tenuring threshold)的信息, 老年代閥值,意思是對象重新生代移動到老年代以前,通過幾回GC(即, 對象晉升前的最大年齡). 上例中,老年代閥值爲15,最大也是15.

以後行表示,對於小於老年代閥值的每個對象年齡,本年齡中對象所佔字節 (若是當前年齡沒有對象,這一行會忽略). 上例中,一次 GC 後倖存對象大約 19 MB, 兩次GC 後倖存對象大約79 KB , 三次GC 後倖存對象大約 3 MB .每行結尾,顯示直到本年齡所有對象大小.因此,最後一行的 total 表示倖存區To 總共被佔用22 MB . 倖存區To 總大小爲 75 MB ,當前老年代閥值爲15,能夠判定在本次GC中,沒有對象會移動到老年代。如今假設下一次GC 輸出爲:

Desired survivor size 75497472 bytes, new threshold 2 (max 15)

  • age 1: 68407384 bytes, 68407384 total
  • age 2: 12494576 bytes, 80901960 total
  • age 3: 79376 bytes, 80981336 total
  • age 4: 2904256 bytes, 83885592 total

對比前一次老年代分佈。明顯的,年齡2和年齡3 的對象還保持在倖存區中,由於咱們看到年齡3和4的對象大小與前一次年齡2和3的相同。同時發現倖存區中,有一部分對象已經被回收,由於本次年齡2的對象大小爲 12MB ,而前一次年齡1的對象大小爲 19 MB。最後能夠看到最近的GC中,有68 MB 新對象,從伊甸園區移動到倖存區。

注意,本次GC 倖存區佔用總大小 84 MB -大於75 MB. 結果,JVM 把老年代閥值從15下降到2,在下次GC時,一部分對象會強制離開倖存區,這些對象可能會被回收(若是他們恰好死亡)或移動到老年代。

五、-XX:InitialTenuringThreshold, -XX:MaxTenuringThreshold and -XX:TargetSurvivorRatio
參數 -XX:+PrintTenuringDistribution 輸出中的部分值能夠經過其它參數控制。經過 -XX:InitialTenuringThreshold 和 -XX:MaxTenuringThreshold 能夠設定老年代閥值的初始值和最大值。另外,能夠經過參數 -XX:TargetSurvivorRatio 設定倖存區的目標使用率.例如 , -XX:MaxTenuringThreshold=10 -XX:TargetSurvivorRatio=90 設定老年代閥值的上限爲10,倖存區空間目標使用率爲90%。

有多種方式,設置新生代行爲,沒有通用準則。咱們必須清楚如下2中狀況:

1. 若是從年齡分佈中發現,有不少對象的年齡持續增加,在到達老年代閥值以前。這表示 -XX:MaxTenuringThreshold 設置過大
2. 若是 -XX:MaxTenuringThreshold 的值大於1,可是不少對象年齡從未大於1.應該看下倖存區的目標使用率。若是倖存區使用率從未到達,這表示對象都被GC回收,這正是咱們想要的。 若是倖存區使用率常常達到,有些年齡超過1的對象被移動到老年代中。這種狀況,能夠嘗試調整倖存區大小或目標使用率。

六、-XX:+NeverTenure and -XX:+AlwaysTenure
最後,咱們介紹2個頗爲少見的參數,對應2種極端的新生代GC狀況.設置參數 -XX:+NeverTenure , 對象永遠不會晉升到老年代.當咱們肯定不須要老年代時,能夠這樣設置。這樣設置風險很大,而且會浪費至少一半的堆內存。相反設置參數 -XX:+AlwaysTenure, 表示沒有幸存區,全部對象在第一次GC時,會晉升到老年代。
沒有合理的場景使用這個參數。能夠在測試環境中,看下這樣設置會發生什麼有趣的事.可是並不推薦使用這些參數.

結論
適當的配置新生代很是重要,有至關多的參數能夠設置新生代。然而,單獨調整新生代,而不考慮老年代是不可能優化成功的。當調整堆和GC設置時,咱們老是應該同時考慮新生代和老年代。

5、老年代垃圾回收參數優化——吞吐量垃圾回收機制

在實踐中咱們發現對於大多數的應用領域,評估一個垃圾收集(GC)算法如何根據以下兩個標準:

  1. 吞吐量越高算法越好
  2. 暫停時間越短算法越好

首先讓咱們來明確垃圾收集(GC)中的兩個術語:吞吐量(throughput)和暫停時間(pause times)。 JVM在專門的線程(GC threads)中執行GC。 只要GC線程是活動的,它們將與應用程序線程(application threads)爭用當前可用CPU的時鐘週期。 簡單點來講:

  • 吞吐量是指應用程序線程用時佔程序總用時的比例。 例如,吞吐量99/100意味着100秒的程序執行時間應用程序線程運行了99秒, 而在這一時間段內GC線程只運行了1秒。
  • 術語」暫停時間」是指一個時間段內應用程序線程讓與GC線程執行而徹底暫停。 例如,GC期間100毫秒的暫停時間意味着在這100毫秒期間內沒有應用程序線程是活動的。

高吞吐量最好由於這會讓應用程序的最終用戶感受只有應用程序線程在作「生產性」工做。 低暫停時間最好由於從最終用戶的角度來看無論是GC仍是其餘緣由致使一個應用被掛起始終是很差的。 這取決於應用程序的類型,有時候甚至短暫的200毫秒暫停均可能打斷終端用戶體驗。 所以,具備低的最大暫停時間是很是重要的,特別是對於一個交互式應用程序。不幸的是」高吞吐量」和」低暫停時間」是一對相互競爭的目標(矛盾)。對於年老代,HotSpot虛擬機提供兩類垃圾收集算法(除了新的G1垃圾收集算法):

  • 第一類算法試圖最大限度地提升吞吐量;
  • 而第二類算法試圖最小化暫停時間。

當年老代中因爲缺少空間致使對象分配失敗時會觸發垃圾收集器(事實上,」分配」的一般是指從年輕代提高到年老代的對象)。 從所謂的」GC根」(GC roots)開始,搜索堆中的可達對象並將其標記爲活着的,以後,垃圾收集器將活着的對象移到年老代的一塊無碎片(non-fragmented)內存塊中,並標記剩餘的內存空間是空閒的。 也就是說,咱們不像複製策略那樣移到一個不一樣的堆區域,像年輕代垃圾收集算法所作的那樣。 相反地,咱們把全部的對象放在一個堆區域中,從而對該堆區域進行碎片整理。 垃圾收集器使用一個或多個線程來執行垃圾收集。 當使用多個線程時,算法的不一樣步驟被分解,使得每一個收集線程大多時候工做在本身的區域而不干擾其餘線程。 在垃圾收集期間,全部的應用程序線程暫停,只有垃圾收集完成以後纔會從新開始。 如今讓咱們來看看跟面向吞吐量垃圾收集算法有關的重要JVM配置參數。

一、-XX:+UseSerialGC
咱們使用該標誌來激活串行垃圾收集器,例如單線程面向吞吐量垃圾收集器。 不管年輕代仍是年老代都將只有一個線程執行垃圾收集。 該標誌被推薦用於只有單個可用處理器核心的JVM。 在這種狀況下,使用多個垃圾收集線程甚至會拔苗助長,由於這些線程將爭用CPU資源,形成同步開銷,卻從未真正並行運行。

二、-XX:+UseParallelGC
有了這個標誌,咱們告訴JVM使用多線程並行執行年輕代垃圾收集。 在我看來,Java 6中不該該使用該標誌由於-XX:+UseParallelOldGC顯然更合適。 須要注意的是Java 7中該狀況改變了一點(詳見本概述),就是-XX:+UseParallelGC能達到-XX:+UseParallelOldGC同樣的效果。

三、-XX:+UseParallelOldGC
該標誌的命名有點不巧,由於」老」聽起來像」過期」。 然而,」老」其實是指年老代,這也解釋了爲何-XX:+UseParallelOldGC要優於-XX:+UseParallelGC:除了激活年輕代並行垃圾收集,也激活了年老代並行垃圾收集。 當指望高吞吐量,而且JVM有兩個或更多可用處理器核心時,我建議使用該標誌。
做爲旁註,HotSpot的並行面向吞吐量垃圾收集算法一般稱爲」吞吐量收集器」,由於它們旨在經過並行執行來提升吞吐量。

四、-XX:ParallelGCThreads
經過-XX:ParallelGCThreads=咱們能夠指定並行垃圾收集的線程數量。 例如,-XX:ParallelGCThreads=6表示每次並行垃圾收集將有6個線程執行。 若是不明確設置該標誌,虛擬機將使用基於可用(虛擬)處理器數量計算的默認值。 決定因素是由Java Runtime。availableProcessors()方法的返回值N,若是N<=8,並行垃圾收集器將使用N個垃圾收集線程,若是N>8個可用處理器,垃圾收集線程數量應爲3+5N/8。
當JVM獨佔地使用系統和處理器時使用默認設置更有意義。 可是,若是有多個JVM(或其餘耗CPU的系統)在同一臺機器上運行,咱們應該使用-XX:ParallelGCThreads來減小垃圾收集線程數到一個適當的值。 例如,若是4個以服務器方式運行的JVM同時跑在在一個具備16核處理器的機器上,設置-XX:ParallelGCThreads=4是明智的,它能使不一樣JVM的垃圾收集器不會相互干擾。

五、-XX:-UseAdaptiveSizePolicy
吞吐量垃圾收集器提供了一個有趣的(但常見,至少在現代JVM上)機制以提升垃圾收集配置的用戶友好性。 這種機制被看作是HotSpot在Java 5中引入的」人體工程學」概念的一部分。 經過人體工程學,垃圾收集器能將堆大小動態變更像GC設置同樣應用到不一樣的堆區域,只要有證據代表這些變更將能提升GC性能。 「提升GC性能」的確切含義能夠由用戶經過-XX:GCTimeRatio和-XX:MaxGCPauseMillis(見下文)標記來指定。
重要的是要知道人體工程學是默認激活的。 這很好,由於自適應行爲是JVM最大優點之一。 不過,有時咱們須要很是清楚對於特定應用什麼樣的設置是最合適的,在這些狀況下,咱們可能不但願JVM混亂咱們的設置。 每當咱們發現處於這種狀況時,咱們能夠考慮經過-XX:-UseAdaptiveSizePolicy停用一些人體工程學。

六、-XX:GCTimeRatio
經過-XX:GCTimeRatio=咱們告訴JVM吞吐量要達到的目標值。 更準確地說,-XX:GCTimeRatio=N指定目標應用程序線程的執行時間(與總的程序執行時間)達到N/(N+1)的目標比值。 例如,經過-XX:GCTimeRatio=9咱們要求應用程序線程在整個執行時間中至少9/10是活動的(所以,GC線程佔用其他1/10)。 基於運行時的測量,JVM將會嘗試修改堆和GC設置以期達到目標吞吐量。 -XX:GCTimeRatio的默認值是99,也就是說,應用程序線程應該運行至少99%的總執行時間。

七、-XX:MaxGCPauseMillis
經過-XX:GCTimeRatio=告訴JVM最大暫停時間的目標值(以毫秒爲單位)。 在運行時,吞吐量收集器計算在暫停期間觀察到的統計數據(加權平均和標準誤差)。 若是統計代表正在經歷的暫停其時間存在超過目標值的風險時,JVM會修改堆和GC設置以下降它們。 須要注意的是,年輕代和年老代垃圾收集的統計數據是分開計算的,還要注意,默認狀況下,最大暫停時間沒有被設置。
若是最大暫停時間和最小吞吐量同時設置了目標值,實現最大暫停時間目標具備更高的優先級。 固然,沒法保證JVM將必定能達到任一目標,即便它會努力去作。 最後,一切都取決於手頭應用程序的行爲。
當設置最大暫停時間目標時,咱們應注意不要選擇過小的值。 正如咱們如今所知道的,爲了保持低暫停時間,JVM須要增長GC次數,那樣可能會嚴重影響可達到的吞吐量。 這就是爲何對於要求低暫停時間做爲主要目標的應用程序(大多數是Web應用程序),我會建議不要使用吞吐量收集器,而是選擇CMS收集器。 CMS收集器是本系列下一部分的主題。

補充:
併發和並行從宏觀上來說都是同時處理多路請求的概念。但併發和並行又有區別,並行是指兩個或者多個事件在同一時刻發生;而併發是指兩個或多個事件在同一時間間隔內發生。(但任一個時刻點上只有一個程序在處理機上運行。)

老年代垃圾回收參數優化——CMS垃圾回收機制(經常使用語web交互應用)

HotSpot JVM的併發標記清理收集器(CMS收集器)的主要目標就是:低應用停頓時間。該目標對於大多數交互式應用很重要,好比web應用。就像吞吐量收集器,CMS收集器處理老年代的對象,然而其操做要複雜得多。吞吐量收集器老是暫停應用程序線程,而且多是至關長的一段時間,然而這可以使該算法安全地忽略應用程序。相比之下,CMS收集器被設計成在大多數時間能與應用程序線程並行執行,僅僅會有一點(短暫的)停頓時間。GC與應用程序並行的缺點就是,可能會出現各類同步和數據不一致的問題。爲了實現安全且正確的併發執行,CMS收集器的GC週期被分爲了好幾個連續的階段。

1)CMS收集器的過程:

CMS收集器的GC週期由6個階段組成。其中4個階段(名字以Concurrent開始的)與實際的應用程序是併發執行的,而其餘2個階段須要暫停應用程序線程。

  1. 初始標記:爲了收集應用程序的對象引用須要暫停應用程序線程,該階段完成後,應用程序線程再次啓動。
  2. 併發標記:從第一階段收集到的對象引用開始,遍歷全部其餘的對象引用。
  3. 併發預清理:改變當運行第二階段時,由應用程序線程產生的對象引用,以更新第二階段的結果。
  4. 重標記:因爲第三階段是併發的,對象引用可能會發生進一步改變。所以,應用程序線程會再一次被暫停以更新這些變化,而且在進行實際的清理以前確保一個正確的對象引用視圖。這一階段十分重要,由於必須避免收集到仍被引用的對象。
  5. 併發清理:全部再也不被應用的對象將從堆裏清除掉。
  6. 併發重置:收集器作一些收尾的工做,以便下一次GC週期能有一個乾淨的狀態。
    一個常見的誤解是,CMS收集器運行是徹底與應用程序併發的。咱們已經看到,事實並不是如此,即便「stop-the-world」階段相對於併發階段的時間很短。

應該指出,儘管CMS收集器爲老年代垃圾回收提供了幾乎徹底併發的解決方案,然而年輕代仍然經過「stop-the-world」方法來進行收集。對於交互式應用,停頓也是可接受的,背後的原理是年輕帶的垃圾回收時間一般是至關短的。

2)挑戰:

當咱們在真實的應用中使用CMS收集器時,咱們會面臨兩個主要的挑戰,可能須要進行調優:

  1. 堆碎片
  2. 對象分配率高
    堆碎片是有可能的,不像吞吐量收集器,CMS收集器並無任何碎片整理的機制。所以,應用程序有可能出現這樣的情形,即便總的堆大小遠沒有耗盡,但卻不能分配對象——僅僅是由於沒有足夠連續的空間徹底容納對象。當這種事發生後,併發算法不會幫上任何忙,所以,萬不得已JVM會觸發Full GC。回想一下,Full GC 將運行吞吐量收集器的算法,從而解決碎片問題——但卻暫停了應用程序線程。所以儘管CMS收集器帶來徹底的併發性,但仍然有可能發生長時間的「stop-the-world」的風險。這是「設計」,而不能避免的——咱們只能經過調優收集器來它的可能性。想要100%保證避免」stop-the-world」,對於交互式應用是有問題的。

第二個挑戰就是應用的對象分配率高。若是獲取對象實例的頻率高於收集器清除堆裏死對象的頻率,併發算法將再次失敗。從某種程度上說,老年代將沒有足夠的可用空間來容納一個從年輕代提高過來的對象。這種狀況被稱爲「併發模式失敗」,而且JVM會執行堆碎片整理:觸發Full GC。

當這些情形之一出如今實踐中時(常常會出如今生產系統中),常常被證明是老年代有大量沒必要要的對象。

  • 一個可行的辦法就是增長年輕代的堆大小,以防止年輕代短生命的對象提早進入老年代。
  • 另外一個辦法就彷佛利用分析器,快照運行系統的堆轉儲,而且分析過分的對象分配,找出這些對象,最終減小這些對象的申請。

下面我看看大多數與CMS收集器調優相關的JVM標誌參數。

一、-XX:+UseConcMarkSweepGC
該標誌首先是激活CMS收集器。默認HotSpot JVM使用的是並行收集器。

二、-XX:UseParNewGC
當使用CMS收集器時,該標誌激活年輕代使用多線程並行執行垃圾回收。這使人很驚訝,咱們不能簡單在並行收集器中重用-XX:UserParNewGC標誌,由於概念上年輕代用的算法是同樣的。然而,對於CMS收集器,年輕代GC算法和老年代GC算法是不一樣的,所以年輕代GC有兩種不一樣的實現,而且是兩個不一樣的標誌。
注:最新的JVM版本,當使用-XX:+UseConcMarkSweepGC時,-XX:UseParNewGC會自動開啓。所以,若是年輕代的並行GC不想開啓,能夠經過設置-XX:-UseParNewGC來關掉。

三、-XX:+CMSConcurrentMTEnabled
當該標誌被啓用時,併發的CMS階段將以多線程執行(所以,多個GC線程會與全部的應用程序線程並行工做)。該標誌已經默認開啓,若是順序執行更好,這取決於所使用的硬件,多線程執行能夠經過-XX:-CMSConcurremntMTEnabled禁用。

四、-XX:ConcGCThreads
標誌-XX:ConcGCThreads=(早期JVM版本也叫-XX:ParallelCMSThreads)定義併發CMS過程運行時的線程數。好比value=4意味着CMS週期的全部階段都以4個線程來執行。儘管更多的線程會加快併發CMS過程,但其也會帶來額外的同步開銷。所以,對於特定的應用程序,應該經過測試來判斷增長CMS線程數是否真的可以帶來性能的提高。

若是還標誌未設置,JVM會根據並行收集器中的-XX:ParallelGCThreads參數的值來計算出默認的並行CMS線程數。該公式是ConcGCThreads = (ParallelGCThreads + 3)/4。所以,對於CMS收集器, -XX:ParallelGCThreads標誌不只影響「stop-the-world」垃圾收集階段,還影響併發階段。

總之,有很多方法能夠配置CMS收集器的多線程執行。正是因爲這個緣由,建議第一次運行CMS收集器時使用其默認設置, 而後若是須要調優再進行測試。只有在生產系統中測量(或類生產測試系統)發現應用程序的暫停時間的目標沒有達到 , 就能夠經過這些標誌應該進行GC調優。

五、-XX:CMSInitiatingOccupancyFraction
當堆滿以後,並行收集器便開始進行垃圾收集,例如,當沒有足夠的空間來容納新分配或提高的對象。對於CMS收集器,長時間等待是不可取的,由於在併發垃圾收集期間應用持續在運行(而且分配對象)。所以,爲了在應用程序使用完內存以前完成垃圾收集週期,CMS收集器要比並行收集器更先啓動。

由於不一樣的應用會有不一樣對象分配模式,JVM會收集實際的對象分配(和釋放)的運行時數據,而且分析這些數據,來決定何時啓動一次CMS垃圾收集週期。爲了引導這一過程, JVM會在一開始執行CMS週期前做一些線索查找。該線索由 -XX:CMSInitiatingOccupancyFraction=來設置,該值表明老年代堆空間的使用率。好比,value=75意味着第一次CMS垃圾收集會在老年代被佔用75%時被觸發。一般CMSInitiatingOccupancyFraction的默認值爲68(以前很長時間的經從來決定的)。

六、-XX:+UseCMSInitiatingOccupancyOnly
咱們用-XX+UseCMSInitiatingOccupancyOnly標誌來命令JVM不基於運行時收集的數據來啓動CMS垃圾收集週期。而是,當該標誌被開啓時,JVM經過CMSInitiatingOccupancyFraction的值進行每一次CMS收集,而不只僅是第一次。然而,請記住大多數狀況下,JVM比咱們本身能做出更好的垃圾收集決策。所以,只有當咱們充足的理由(好比測試)而且對應用程序產生的對象的生命週期有深入的認知時,才應該使用該標誌。

七、-XX:+CMSClassUnloadingEnabled
相對於並行收集器,CMS收集器默認不會對永久代進行垃圾回收。若是但願對永久代進行垃圾回收,可用設置標誌-XX:+CMSClassUnloadingEnabled。在早期JVM版本中,要求設置額外的標誌-XX:+CMSPermGenSweepingEnabled。注意,即便沒有設置這個標誌,一旦永久代耗盡空間也會嘗試進行垃圾回收,可是收集不會是並行的,而再一次進行Full GC。

八、-XX:+CMSIncrementalMode
該標誌將開啓CMS收集器的增量模式。增量模式常常暫停CMS過程,以便對應用程序線程做出徹底的讓步。所以,收集器將花更長的時間完成整個收集週期。所以,只有經過測試後發現正常CMS週期對應用程序線程干擾太大時,才應該使用增量模式。因爲現代服務器有足夠的處理器來適應併發的垃圾收集,因此這種狀況發生得不多。

九、-XX:+ExplicitGCInvokesConcurrent and -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
現在,被普遍接受的最佳實踐是避免顯式地調用GC(所謂的「系統GC」),即在應用程序中調用system.gc()。然而,這個建議是無論使用的GC算法的,值得一提的是,當使用CMS收集器時,系統GC將是一件很不幸的事,由於它默認會觸發一次Full GC。幸運的是,有一種方式能夠改變默認設置。標誌-XX:+ExplicitGCInvokesConcurrent命令JVM不管何時調用系統GC,都執行CMS GC,而不是Full GC。第二個標誌-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses保證當有系統GC調用時,永久代也被包括進CMS垃圾回收的範圍內。所以,經過使用這些標誌,咱們能夠防止出現意料以外的」stop-the-world」的系統GC。

十、-XX:+DisableExplicitGC
然而在這個問題上…這是一個很好提到- XX:+ DisableExplicitGC標誌的機會,該標誌將告訴JVM徹底忽略系統的GC調用(無論使用的收集器是什麼類型)。對於我而言,該標誌屬於默認的標誌集合中,能夠安全地定義在每一個JVM上運行,而不須要進一步思考。

參考: http://ifeve.com/useful-jvm-flags/

相關文章
相關標籤/搜索