Hotspot JVM的常用選項


原文傳送門

選項的分類

Hotspot JVM提供以下三大類選項:

  1. 標準選項:這類選項的功能是很穩定的,在後續版本中也不太會發生變化。運行java或者java -help可以看到所有的標準選項。所有的標準選項都是以-開頭,比如-version, -server等。
  2. X選項:比如-Xms。這類選項都是以-X開頭,可能由於這個原因它們被稱爲X選項。運行java -X命令可以看到所有的X選項。這類選項的功能還是很穩定,但官方的說法是它們的行爲可能會在後續版本中改變,也有可能不在後續版本中提供了。
  3. XX選項:這類選項是屬於實驗性,主要是給JVM開發者用於開發和調試JVM的,在後續的版本中行爲有可能會變化。

XX選項的語法

如果是布爾類型的選項,它的格式爲-XX:+flag或者-XX:-flag,分別表示開啓和關閉該選項。
針對非布爾類型的選項,它的格式爲-XX:flag=value
在瞭解這些約定的規範後,我們就可以來看看一些比較常用的選項了。

指定JVM的類型:-server,-client

Hotspot JVM有兩種類型,分別是server和client。它們的區別是Server VM的初始堆空間會大一些,默認使用的時並行垃圾回收器。Client VM相對來講會保守一些,初始堆空間會小一些,使用串行的垃圾回收器,它的目標是爲了讓JVM的啓動速度更快。

JVM在啓動的時候會根據硬件和操作系統會自動選擇使用Server還是Client類型的JVM。

  • 在32位Windows系統上,不論硬件配置如何,都默認使用Client類型的JVM。
  • 在其他32位操作系統上,如果機器配置有2GB集羣以上的內存同時有2個以上的CPU,則默認會使用Server類型的JVM
  • 64位機器上只有Server類型的JVM。也就是說Client類型的JVM只在32位機器上提供。
  • 你也可以使用-server和-client選項來指定JVM的類型,不過只在32位的機器上有效,原因見上面一條。
    詳細內容請參見:http://docs.oracle.com/javase/7/docs/technotes/guides/vm/server-class.html

指定JIT編譯器的模式:-Xint,-Xcomp,-Xmixed

我們知道Java是一種解釋型語言,但是隨着JIT技術的進步,它能在運行時將Java的字節碼編譯成本地代碼。以下是幾個相關的選項:

  • -Xint表示禁用JIT,所有字節碼都被解釋執行,這個模式的速度最慢的。
  • -Xcomp表示所有字節碼都首先被編譯成本地代碼,然後再執行。
  • -Xmixed,默認模式,讓JIT根據程序運行的情況,有選擇地將某些代碼編譯成本地代碼。

-Xcomp和-Xmixed到底誰的速度快,針對不同的程序可能有不同的結果,基本還是推薦用默認模式。

-version和-showversion

-version就是查看當前機器的java是什麼版本,是什麼類型的JVM(Server/Client),採用的是什麼執行模式。比如,在我的機器上的結果如下:

$ java -version
java version "1.7.0_71"
Java(TM) SE Runtime Environment (build 1.7.0_71-b14)
Java HotSpot(TM) 64-Bit Server VM (build 24.71-b01, mixed mode)

表示我機器上java是運行在mixed模式下的Server VM。

-showversion的作用是在運行一個程序的時候首先把JVM的版本信息打印出來,這樣便於問題診斷。個人建議Server類型的程序都把這個選項打開,這樣可以發現一些配置問題,比如程序需要JDK1.7才能運行,而有的機器上裝有多個JDK的版本,打開這個選項可以避免使用了錯誤版本的Java。

查看XX選項的值: -XX:+PrintCommandLineFlags, -XX:+PrintFlagsInitial和-XX:+PrintFlagsFinal

與-showversion類似,-XX:+PrintCommandLineFlags可以讓在程序運行前打印出用戶手動設置或者JVM自動設置的XX選項,建議加上這個選項以輔助問題診斷。比如在我的機器上,JVM自動給配置了初始的和最大的HeapSize以及其他的一些選項:

$ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintCommandLineFlags -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.7.0_71"
Java(TM) SE Runtime Environment (build 1.7.0_71-b14)
Java HotSpot(TM) 64-Bit Server VM (build 24.71-b01, mixed mode)

相關另外兩個選項:-XX:+PrintFlagsInitial表示打印出所有XX選項的默認值,-XX:+PrintFlagsFinal表示打印出XX選項在運行程序時生效的值。

內存大小相關的選項

  • -Xms 設置初始堆的大小,也是最小堆的大小,它等價於:-XX:InitialHeapSize
  • -Xmx 設置最大堆的大小,它等價於-XX:MaxHeapSize。
    比如,下面這條命令就是設置堆的初始值爲128m,最大值爲2g。
java -Xms128m -Xmx2g MyApp

如果堆的初始值和最大值不一樣的話,JVM會根據程序的運行情況,自動調整堆的大小,這可能會影響到一些效率。針對服務端程序,一般是把堆的最小值和最大值設置爲一樣來避免堆擴展和收縮對性能的影響。

  • -XX:PermSize 用來設置永久區的初始大小

  • -XX:MaxPermSize 用來設置永久區的最大值
    永久區是存放類以及常量池的地方,如果程序需要加載的class數量非常多的話,就需要增大永久區的大小。

  • -Xss 設置線程棧的大小,線程棧的大小會影響到遞歸調用的深度,同時也會影響到能同時開啓的線程數量。

OutofMemory(OOM)相關的選項

如果程序發生了OOM後,JVM可以配置一些選項來做些善後工作,比如把內存給dump下來,或者自動採取一些別的動作。

  • -XX:+HeapDumpOnOutOfMemoryError 表示在內存出現OOM的時候,把Heap轉存(Dump)到文件以便後續分析,文件名通常是java_pid.hprof,其中pid爲該程序的進程號。
  • -XX:HeapDumpPath=: 用來指定heap轉存文件的存儲路徑,需要指定的路徑下有足夠的空間來保存轉存文件。
  • -XX:OnOutOfMemoryError 用來指定一個可行性程序或者腳本的路徑,當發生OOM的時候,去執行這個腳本。
    比如,下面的命令可以使得在發生OOM的時候,Heap被轉存到文件/tmp/heapdump.hprof,同時執行Home目錄中的cleanup.sh文件。
$ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError ="sh ~/cleanup.sh" MyApp

個人覺得幾個選項還是非常有用的,它可以使得你有相關的信息來分析OOM的根源。

新生代相關的選項

在介紹新生代相關的選項前,先簡要介紹下Hotspot VM的Heap分代的背景知識。很多面向對象程序在運行時都具有如下兩點特徵:

  1. 新創建的對象通常不會存活很長時間,也就是夭折了。
  2. 很少有老對象引用到新對象。

基於這裏兩點,把新老對象分別放在不同的區域(分別叫做新生代和老生代)可以針對新老對象的特點使用不同的回收算法,同時在回收新對象的時候不用遍歷老對象,從而提高垃圾回收的效率。

在Hotspot JVM中,它進一步地將新生代分成了三個區域,一個稍大的區域Eden和兩個較小但大小相等的Survivor區域(分別叫做From和To)。一般來講,新對象首先分配在Eden區域,當Eden區域滿的時候,會執行一次Minor GC。MinorGC使用的是標記-拷貝算法。垃圾回收器會首先標記Eden和From區域中還存活的對象,然後把它們全部移動到To區域,這樣Eden和From區域的空間就可以全部回收了,最後再將指向From和To區域的指針交換一下。

下圖展示了MinorGC的流程,綠色區域表示空閒空間,紅色表示活動對象,黃色表示可以回收的對象。
在這裏插入圖片描述
簡要總結一下,對象在新生代的生命週期是,它首先在Eden區域誕生,如果對象在MinorGC時還存活的話,就移動到Survivor區域。在後續的MinorGC的時候,如果對象還繼續存活的話,就在兩個Survivor區域將倒騰。那對象什麼時候會被移動到老生代呢?有以下條件:

  1. Survivor區域中存活對象佔用Survivor空間達到了指定的閾值。
  2. 對象在Survivor空間每倒騰一次其年齡就加1,如果一個對象的年齡達到了一個閾值,也會被移動到老生代。
  3. 大對象會在創建的時候就會被直接放到老生代。

由此可見,新生代的空間大小很重要:如果新生代空間過小,就會導致對象很快就被移動到老生代,從而使得某些原本可以及時回收的對象存活的時間過長,而且老生代回收的代價更大。那相反,如果新生代空間過大,就會使得某些存活時間長的對象在新生代倒騰了很多次,影響到新生代回收垃圾的效率。這就需要根據應用的特點,找到一個合適的值。Hotspot提供瞭如下一些選項來調節新生代的參數:

  • -XX:NewSize和-XX:MaxNewSize分別用來設置新生代的最小和最大值。需要注意的是,新生代是JVM堆的一部分,新生代的空間大小不能大於老生代的大小,因爲在極端的情況下,新生代中對象可能會被全部移到老生代,因此-XX:MaxNewSize最大隻能設爲-Xmx的一半。
  • -XX:NewRatio用來設置老生代和新生代大小的比例,比如-XX:NewRatio=2表示1/3的Heap是新生代,2/3的Heap是老生代。使用這個選項的好處是新生代的大小也能隨着Heap的變化而變化。
  • -XX:SurvivorRatio用來設置新生代中Eden和Survivor空間大小的比例,需要注意的是有兩個Survivor。比如-XX:SurvivorRatio=8表示Eden區域在新生代的8/10,兩個Survivor分別佔1/10。調節Survivor空間的時候也注意要折中,如果Survivor空間小的話,那麼很有可能在一次MinorGC的時候Survivor空間就滿了,從而對象就被移到了老生代;如果Survivor空間大的話,那麼Eden區域就小了,從而導致MinorGC的發生得更頻繁。
    總得來說,調節新生代的目標是:1)避免對象過早地被移到了老生代 2)也要避免需要長期存活的對象在新生代呆的時間過長,這會提高MinorGC發生的頻率以及增加單次MinorGC的時間。這需要針對程序的運行情況做一些分析。接下來就介紹了一個參數來分析新生代對象年齡的分佈。

-XX:+PrintTenuringDistribution

-XX:+PrintTenuringDistribution讓JVM在每次MinorGC後打印出Survivor空間中的對象的年齡分佈。比如:

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

從第一行中可以看出JVM期望的Survivor空間佔用爲72M,對象被移到老年代中的年齡閾值爲15。其中期望的Survivor空間大小爲Survivor空間大小 x -XX:TargetSurvivorRatio的值。

接下來的一行,表示年齡爲1的對象約19M,年齡爲2的對象約79k,年齡爲3的對象約爲2.9M,每行後面的數值表示所有小於等於該行年齡的對象的總共大小,比如最後一行就表示所有年齡小於等於3的對象的總共大小爲約22M(等於所有年齡對象大小的和)。因爲目前Survivor空間中對象的大小22M小於期望Survivor空間的大小72M,所以沒有對象會被移到老年代。

假設下一次MinorGC後的輸出結果爲:

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

上次MinorGC後還存活的對象在這次MinorGC年齡都增加了1,可以看到上次年齡爲2和3的對象(對應在這次GC後的年齡爲3和4)依然存在(大小未變),而一部分上次對象年齡爲1的對象在這次GC時被回收了。同時可以看到這次新增了約68M的新對象。這次MinorGC後Survivor區域中對象總的大小爲約83M,大於了期望的Survivor空間的大小72M,因此它就把對象移到老年代的年齡的閾值調整爲2,在下次MinorGC時一部分對象就會被移到老年代了。

相關的調整選項有:

  • -XX:InitialTenuringThreshold 表示對象被移到老年代的年齡閾值的初始值
  • -XX:MaxTenuringThreshold 表示對象被移到老年代的年齡閾值的最大值
  • -XX:TargetSurvivorRatio 表示MinorGC結束了Survivor區域中佔用空間的期望比例。

這些參數的調節沒有統一的標準,但是有兩點可以借鑑:

  1. 如果Survivor中對象的年齡分佈顯示很多對象在經歷了多次GC最終年齡達到了-XX:MaxTenuringThreshold才被移到老年代,這可能說明-XX:MaxTenuringThreshold設置得過大,也有可能是Survivor的空間過大。
  2. 如果-XX:MaxTenuringThreshold的值大於1,但是很多對象年齡都不大於1,那就得關注一下期望的Survivor空間。如果每次GC後Survivor中對象的大小都沒有超過期望的Survivor空間大小,則說明GC工作得很好。反之,則說明可能Survivor空間小了,使得新生成的對象很快就被移到了老年代了。

吞吐量優先收集器的相關選項

衡量JVM垃圾收集器的兩個基本指標是吞吐量和停頓時間。吞吐量是指執行用戶代碼的時間佔總的時間的比例,總的時間包括執行用戶代碼的時間和垃圾回收佔用的時間。在垃圾回收的時候執行用戶代碼的線程必須暫停,這會導致程序暫時失去響應。停頓時間就是衡量垃圾回收時造成的用戶線程暫停的時間。這兩個指標是在一定程度是相互矛盾的,不可能讓一個程序的吞吐量很高的同時停頓時間也短,只能以優先選擇一個目標或者折中一下。因此,不同的垃圾回收器會有不同的側重點。

在Hotspot JVM中,側重於吞吐量的垃圾回收器是Parallel Scavenge,它的相關選項如下:

  • -XX:+UseParallelOldGC 表示新生代和老生代都使用並行回收器,其中的Old表示老生代的意思,而不是舊的意思。
  • -XX:ParallelGCThreads=n 表示配置多少個線程來回收垃圾。默認的配置是如果處理器的個數小於8,那麼就是處理器的個數;如果處理器大於8,它的值就是3+5N/8。也可以根據程序的需要去設置這個值,比如你的機器有16核,上面有4個Java程序,那麼設置將這個值設置爲4比較合理,因爲JVM不會去探測同一機器上有多少個Java程序。
  • -XX:UseAdaptiveSizePolicy 表示是否開啓自適應策略,打開這個開關後,JVM自動調節JVM的新生代大小,Eden和Survivor的比例等參數。用戶只需要設置期望的吞吐量(-XX:GCTimeRatio)和期望的停頓時間(-XX:MaxGCPauseMillis)。然後,JVM會盡量去向用戶期望的方向去優化。
    此外,如果機器只有一個核的話,採用並行回收器可能得不償失,因爲多個回收線程會爭搶CPU資源,反而造成更大的消耗。這時,就最好採用串行回收器,相關的參數是-XX:+UseSerialGC

CMS收集器

CMS收集器(ConcurrentMarkandSweep),是一個關注系統停頓時間的收集器。它的主要思想是把收集器分成了不同的階段,其中某些階段是可以用戶程序並行的,從而減少了整體的系統停頓時間。它主要分成了以下幾個階段:

  • 初始標記 initial mark
  • 併發標記 concurrent mark
  • 重新標記 remark
  • 併發清理 concurrent clean
  • 併發重置 concurrent reset

凡是名字以併發開頭的階段都是可以和用戶線程並行的,其他階段也是要暫停用戶程序線程。
CMS雖然能減少系統的停頓時間,但是它也有其缺點:

  1. 從它的名字可以看出,它是一個標記-清除收集器,也就說運行了一段時間後,內存會產生碎片,從而導致無法找到連續空間來分配大對象。
  2. CMS收集器在運行過程中會佔用一些內存,同時系統還在運行,如果系統產生新對象的速度比CMS清理的速度快的話,會導致CMS運行失敗。

當上面的任何一種情況發生的時候,JVM就會觸發一次Full GC,會導致JVM停頓較長時間。

它的相關選項如下:

  • -XX:+UseConcMarkSweepGC 表示老年代開啓CMS收集器,而新生代默認會使用並行收集器。
  • -XX:ConcGCThreads 指定用多少個線程來執行CMS的並非階段。
  • -XX:CMSInitiatingOccupancyFraction 指定在老生代用掉多少內存後開始進行垃圾回收。與吞吐量優先的回收器不同的是,吞吐量優先的回收器在老生代內存用盡了以後纔開始進行收集,這對CMS來講是不行的,因爲吞吐量優先的垃圾回收器運行的時候會停止所有用戶線程,所以不會產生新的對象,而CMS運行的時候,用戶線程還有可能產生新的對象,所以不能等到內存用光後纔開始運行。比如-XX:CMSInitiatingOccupancyFraction=75表示老生代用掉75%後開始回收垃圾。默認值是68。
  • -XX:+ExplicitGCInvokesConcurrent 如果在代碼裏面顯式調用System.gc(),那麼它還是會執行Full GC從而導致用戶線程被暫停。採用這個選項使得顯式觸發GC的時候還是使用CMS收集器。
  • -XX:+DisableExplicitGC 一個相關的選項,這個選項是禁止顯式調用GC

GC日誌相關的選項

分析GC問題不可避免地要查看GC日誌,下面是一些GC日誌相關的選項:

  • -XX:+PrintGC,等同於-verbose:gc 表示打開簡化的GC日誌,相關輸出如下:
[GC 425355K->351685K(506816K), 0.2175300 secs] 
[Full GC 500561K->456058K(506816K), 0.6421920 secs]

其中以GC開頭的行表示發生了一次Minor GC,後面的數字表示收集前後Heap空間的佔用量,圓括號裏面表示Heap大小,最後的數字表示用了多少時間。比如:上面的例子中,表示在這次GC新生代空間佔用從425355K降到了351685K,總的新生代空間爲506816K,這次GC耗時0.22秒。
通過這個選項只能看到一些基本信息,而且所有收集器的輸出在這個模式下都是一樣的。

  • -XX:+PrintGCDetails 這個選項會打印出更多的GC日誌,不同的收集器產生的日誌會不一樣。因此,在後續的文章中再介紹不同收集器的日誌格式。

  • -XX:+PrintGCTimeStamps and -XX:+PrintGCDateStamps 這兩個選項把GC的時間戳顯示在GC的日誌中。其中,-XX:+PrintGCTimeStamps打印GC發生的時間相對於JVM啓動的時間,-XX:+PrintGCDateStamps表示打印出GC發生的具體時間。

比如,以下是-XX:+PrintGCTimeStamps的輸出

0,185: [GC 66048K->53077K(251392K), 0,0977580 secs]
0,323: [GC 119125K->114661K(317440K), 0,1448850 secs]
0,603: [GC 246757K->243133K(375296K), 0,2860800 secs]

以下是兩個都打開後的輸出

2014-12-26T17:52:38.613-0800: 3.395: [GC 139776K->58339K(506816K), 0.1442900 secs]
  • -Xloggc: 表示把GC日誌寫入到一個文件中去,而不是打印到標準輸出中。
    需要注意的是:這些和GC日誌相關的選項可以在JVM已經啓動後再開啓,可以通過jinfo這個工具去設置。具體可以參見jinfo的幫助文件。這樣就可以在需要診斷問題的時候再開啓GC日誌。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章