[HotSpot VM] JVM調優的"標準引數"的各種陷阱

五柳-先生發表於2016-04-21
開個帖大家來討論下自己遇到過的情況吧?我在頂樓舉幾個例子。 

開這帖的目的是想讓大家瞭解到,所謂“標準引數”是件很微妙的事情。確實有許多前輩經過多年開發積累下了許多有用的調優經驗,但向他們問“標準引數”並照單全收是件危險的事情。 

前輩們提供的“標準引數”或許適用於他們的應用場景,他們或許也知道這些引數裡隱含的陷阱;但聽眾卻不一定知道各種引數背後的緣由。 

原則上說,在生產環境使用非標準引數(這裡指的是在各JDK/JRE實現特有的、相互之間不通用的引數)應該儘量避免。這些引數與具體實現密切相關,不是光了解很抽象的“JVM原理”就足以理解的;即便在同一系列的JDK/JRE實現中,非標準引數也不保證在各版本間有一樣的作用;而且許多人只看名字就猜想引數的左右,做“調優”卻適得其反。 

非標準引數的預設值在不同版本間或許會悄然發生變化。這些變化的背後多半有合理的理由。設了一大堆非標準引數、不明就裡的同學在升級JDK/JRE的時候也容易掉坑裡。 

下面用Oracle/Sun JDK 6來舉幾個例子。這帖頂樓裡的討論如果沒明確指出JDK版本的都是指Oracle/Sun JDK 6(OpenJDK 6也可以算在內)。 
經驗不一定適用於Sun JDK 1.4.2、Sun JDK 5、Oracle JDK 7。 

更新:之前用.hotspotrc和.hotspot_compiler引數來配置HotSpot VM的預設引數的同學們留意:在較新版本的Oracle JDK6/7/8上需要顯式用-XX:Flags=.hotspotrc與-XX:CompileCommandFile=.hotspot_compiler才可以使用這倆配置檔案,否則會忽略它們。 

====================================================================== 

0、各引數的預設值 

在討論HotSpot VM的各引數的陷阱前,大家應該先了解HotSpot VM到底有哪些引數可以設定,這些引數的預設值都是什麼。 

有幾種辦法可以幫助大家獲取引數的資訊。首先為了大致瞭解都有些什麼引數可以設定,可以參考HotSpot VM裡的各個globals.hpp檔案:(以下連結取自HotSpot 20.0,與JDK 6 update 25對應) 
globals.hpp 
globals_extension.hpp 
c1_globals.hpp 
c1_globals_linux.hpp 
c1_globals_solaris.hpp 
c1_globals_sparc.hpp 
c1_globals_windows.hpp 
c1_globals_x86.hpp 
c2_globals.hpp 
c2_globals_linux.hpp 
c2_globals_solaris.hpp 
c2_globals_sparc.hpp 
c2_globals_windows.hpp 
c2_globals_x86.hpp 
g1_globals.hpp 
globals_linux.hpp 
globals_linux_sparc.hpp 
globals_linux_x86.hpp 
globals_linux_zero.hpp 
globals_solaris.hpp 
globals_solaris_sparc.hpp 
globals_solaris_x86.hpp 
globals_sparc.hpp 
globals_windows.hpp 
globals_windows_x86.hpp 
globals_x86.hpp 
globals_zero.hpp 
shark_globals.hpp 
shark_globals_zero.hpp 
arguments.cpp 

然後是 -XX:+PrintCommandLineFlags 。這個引數的作用是顯示出VM初始化完畢後所有跟最初的預設值不同的引數及它們的值。 
這個引數至少在Sun JDK 5上已經開始支援,Oracle/Sun JDK 6以及Oracle JDK 7上也可以使用。Sun JDK 1.4.2還不支援這個引數。 
例子: 
Command prompt程式碼  收藏程式碼
  1. $ java -XX:+PrintCommandLineFlags  
  2. VM option '+PrintCommandLineFlags'  
  3. -XX:InitialHeapSize=57344000 -XX:MaxHeapSize=917504000 -XX:ParallelGCThreads=4 -XX:+PrintCommandLineFlags -XX:+UseCompressedOops -XX:+UseParallelGC   

《Java Performance》一書主要是用這個引數來介紹HotSpot VM各引數的效果的。 

接著是 -XX:+PrintFlagsFinal 。前一個引數只顯示跟預設值不同的,而這個引數則可以顯示所有可設定的引數及它們的值。不過這個引數本身只從JDK 6 update 21開始才可以用,之前的Oracle/Sun JDK則用不了。 
可以設定的引數預設是不包括diagnostic或experimental系的。要在-XX:+PrintFlagsFinal的輸出裡看到這兩種引數的資訊,分別需要顯式指定-XX:+UnlockDiagnosticVMOptions / -XX:+UnlockExperimentalVMOptions 。 

再下來,-XX:+PrintFlagsInitial 。這個引數顯示在處理引數之前所有可設定的引數及它們的值,然後直接退出程式。“引數處理”包括許多步驟,例如說檢查引數之間是否有衝突,通過ergonomics調整某些引數的值,之類的。 
結合-XX:+PrintFlagsInitial與-XX:+PrintFlagsFinal,對比兩者的差異,就可以知道ergonomics對哪些引數做了怎樣的調整。 
這兩個引數的例子: 
Command prompt程式碼  收藏程式碼
  1. $ java -version  
  2. java version "1.6.0_29"  
  3. Java(TM) SE Runtime Environment (build 1.6.0_29-b11)  
  4. Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02, mixed mode)  
  5. $ java -XX:+PrintFlagsInitial | grep UseCompressedOops  
  6.      bool UseCompressedOops                         = false           {lp64_product}        
  7. $ java -XX:+PrintFlagsFinal | grep UseCompressedOops  
  8.      bool UseCompressedOops                        := true            {lp64_product}        

Oracle/Sun JDK 6從update 23開始會由ergonomics在合適的條件下預設啟用壓縮指標功能。這個例子演示的是UseCompressedOops的初始預設值是false,由PrintFlagsInitial的輸出可以看到;然後經過ergonomics自動調整後,最終採用的預設值是true,由PrintFlagsFinal的輸出可以看到。 
輸出裡“=”表示使用的是初始預設值,而“:=”表示使用的不是初始預設值,可能是命令列傳進來的引數、配置檔案裡的引數或者是ergonomics自動選擇了別的值。 


除了在VM啟動時傳些特殊的引數讓它列印出自己的各引數外,jinfo -flag 可以用來檢視某個引數的值,也可以用來設定manageable系引數的值。請參考這帖的例子:通過jinfo工具在full GC前後做heap dump 

之前發過某些環境中HotSpot VM的各引數的預設值,可以參考一下。 

====================================================================== 

1、-XX:+DisableExplicitGC 與 NIO的direct memory 

很多人都見過JVM調優建議裡使用這個引數,對吧?但是為什麼要用它,什麼時候應該用而什麼時候用了會掉坑裡呢? 

首先要了解的是這個引數的作用。在Oracle/Sun JDK這個具體實現上,System.gc()的預設效果是引發一次stop-the-world的full GC,對整個GC堆做收集。有幾個引數可以改變預設行為,之前發過一帖簡單描述過,這裡就不重複了。關鍵點是,用了-XX:+DisableExplicitGC引數後,System.gc()的呼叫就會變成一個空呼叫,完全不會觸發任何GC(但是“函式呼叫”本身的開銷還是存在的哦~)。 

為啥要用這個引數呢?最主要的原因是為了防止某些手賤的同學在程式碼裡到處寫System.gc()的呼叫而干擾了程式的正常執行吧。有些應用程式本來可能正常跑一天也不會出一次full GC,但就是因為有人在程式碼裡呼叫了System.gc()而不得不間歇性被暫停。也有些時候這些呼叫是在某些庫或框架裡寫的,改不了它們的程式碼但又不想被這些呼叫干擾也會用這引數。 

OK。看起來這引數應該總是開著嘛。有啥坑呢? 

其中一種情況是下述三個條件同時滿足時會發生的: 
1、應用本身在GC堆內的物件行為良好,正常情況下很久都不發生full GC; 
2、應用大量使用了NIO的direct memory,經常、反覆的申請DirectByteBuffer 
3、使用了-XX:+DisableExplicitGC
 
能觀察到的現象是: 
Log程式碼  收藏程式碼
  1. java.lang.OutOfMemoryError: Direct buffer memory  
  2.     at java.nio.Bits.reserveMemory(Bits.java:633)  
  3.     at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)  
  4.     at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)  
  5. ...  


做個簡單的例子來演示這現象: 
Java程式碼  收藏程式碼
  1. import java.nio.*;  
  2.   
  3. public class DisableExplicitGCDemo {  
  4.   public static void main(String[] args) {  
  5.     for (int i = 0; i < 100000; i++) {  
  6.       ByteBuffer.allocateDirect(128);  
  7.     }  
  8.     System.out.println("Done");  
  9.   }  
  10. }  

然後編譯、執行之: 
Command prompt程式碼  收藏程式碼
  1. $ java -version  
  2. java version "1.6.0_25"  
  3. Java(TM) SE Runtime Environment (build 1.6.0_25-b06)  
  4. Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)  
  5. $ javac DisableExplicitGCDemo.java   
  6. $ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC -XX:+DisableExplicitGC DisableExplicitGCDemo  
  7. Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory  
  8.     at java.nio.Bits.reserveMemory(Bits.java:633)  
  9.     at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)  
  10.     at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)  
  11.     at DisableExplicitGCDemo.main(DisableExplicitGCDemo.java:6)  
  12. $ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC DisableExplicitGCDemo  
  13. [GC 10996K->10480K(120704K), 0.0433980 secs]  
  14. [Full GC 10480K->10415K(120704K), 0.0359420 secs]  
  15. Done  

可以看到,同樣的程式,不帶-XX:+DisableExplicitGC時能正常完成執行,而帶上這個引數後卻出現了OOM。 
例子裡用-XX:MaxDirectMemorySize=10m限制了DirectByteBuffer能分配的空間的限額,以便問題更容易展現出來。不用這個引數就得多跑一會兒了。 

在這個例子裡,main()裡的迴圈不斷申請DirectByteBuffer但並沒有引用、使用它們,所以這些DirectByteBuffer應該剛建立出來就已經滿足被GC的條件,等下次GC執行的時候就應該可以被回收。 

實際上卻沒這麼簡單。DirectByteBuffer是種典型的“冰山”物件,也就是說它的Java物件雖然很小很無辜,但它背後卻會關聯著一定量的native memory資源,而這些資源並不在GC的控制之下,需要自己注意控制好。對JVM如何使用native memory不熟悉的同學可以參考去年JavaOne上IBM的一個演講,“Where Does All the Native Memory Go”。 

Oracle/Sun JDK的實現裡,DirectByteBuffer有幾處值得注意的地方。 
1、DirectByteBuffer沒有finalizer,它的native memory的清理工作是通過sun.misc.Cleaner自動完成的。 

2、sun.misc.Cleaner是一種基於PhantomReference的清理工具,比普通的finalizer輕量些。對PhantomReference不熟悉的同學請參考Bob Lee最近幾年在JavaOne上做的演講,"The Ghost in the Virtual Machine: A Reference to References"今年的JavaOne上他也講了同一個主題,內容比前幾年的稍微更新了些。PPT可以從連結裡的頁面下載到。 
Java程式碼  收藏程式碼
  1. /** 
  2.  * General-purpose phantom-reference-based cleaners. 
  3.  * 
  4.  * <p> Cleaners are a lightweight and more robust alternative to finalization. 
  5.  * They are lightweight because they are not created by the VM and thus do not 
  6.  * require a JNI upcall to be created, and because their cleanup code is 
  7.  * invoked directly by the reference-handler thread rather than by the 
  8.  * finalizer thread.  They are more robust because they use phantom references, 
  9.  * the weakest type of reference object, thereby avoiding the nasty ordering 
  10.  * problems inherent to finalization. 
  11.  * 
  12.  * <p> A cleaner tracks a referent object and encapsulates a thunk of arbitrary 
  13.  * cleanup code.  Some time after the GC detects that a cleaner's referent has 
  14.  * become phantom-reachable, the reference-handler thread will run the cleaner. 
  15.  * Cleaners may also be invoked directly; they are thread safe and ensure that 
  16.  * they run their thunks at most once. 
  17.  * 
  18.  * <p> Cleaners are not a replacement for finalization.  They should be used 
  19.  * only when the cleanup code is extremely simple and straightforward. 
  20.  * Nontrivial cleaners are inadvisable since they risk blocking the 
  21.  * reference-handler thread and delaying further cleanup and finalization. 
  22.  * 
  23.  * 
  24.  * @author Mark Reinhold 
  25.  * @version %I%, %E% 
  26.  */  

重點是這兩句:"A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code.  Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner." 
Oracle/Sun JDK 6中的HotSpot VM只會在old gen GC(full GC/major GC或者concurrent GC都算)的時候才會對old gen中的物件做reference processing,而在young GC/minor GC時只會對young gen裡的物件做reference processing。 
(死在young gen中的DirectByteBuffer物件會在young GC時被處理的例子,請參考這裡:https://gist.github.com/1614952) 
也就是說,做full GC的話會對old gen做reference processing,進而能觸發Cleaner對已死的DirectByteBuffer物件做清理工作。而如果很長一段時間裡沒做過GC或者只做了young GC的話則不會在old gen觸發Cleaner的工作,那麼就可能讓本來已經死了的、但已經晉升到old gen的DirectByteBuffer關聯的native memory得不到及時釋放。 

3、為DirectByteBuffer分配空間過程中會顯式呼叫System.gc(),以期通過full GC來強迫已經無用的DirectByteBuffer物件釋放掉它們關聯的native memory: 
Java程式碼  收藏程式碼
  1. // These methods should be called whenever direct memory is allocated or  
  2. // freed.  They allow the user to control the amount of direct memory  
  3. // which a process may access.  All sizes are specified in bytes.  
  4. static void reserveMemory(long size) {  
  5.   
  6.     synchronized (Bits.class) {  
  7.         if (!memoryLimitSet && VM.isBooted()) {  
  8.             maxMemory = VM.maxDirectMemory();  
  9.             memoryLimitSet = true;  
  10.         }  
  11.         if (size <= maxMemory - reservedMemory) {  
  12.             reservedMemory += size;  
  13.             return;  
  14.         }  
  15.     }  
  16.   
  17.     System.gc();  
  18.     try {  
  19.         Thread.sleep(100);  
  20.     } catch (InterruptedException x) {  
  21.         // Restore interrupt status  
  22.         Thread.currentThread().interrupt();  
  23.     }  
  24.     synchronized (Bits.class) {  
  25.         if (reservedMemory + size > maxMemory)  
  26.             throw new OutOfMemoryError("Direct buffer memory");  
  27.         reservedMemory += size;  
  28.     }  
  29.   
  30. }  


這幾個實現特徵使得Oracle/Sun JDK 6依賴於System.gc()觸發GC來保證DirectByteMemory的清理工作能及時完成。如果開啟了-XX:+DisableExplicitGC,清理工作就可能得不到及時完成,於是就有機會見到direct memory的OOM,也就是上面的例子演示的情況。我們這邊在實際生產環境中確實遇到過這樣的問題。 

教訓是:如果你在使用Oracle/Sun JDK 6,應用裡有任何地方用了direct memory,那麼使用-XX:+DisableExplicitGC要小心。如果用了該引數而且遇到direct memory的OOM,可以嘗試去掉該引數看是否能避開這種OOM。如果擔心System.gc()呼叫造成full GC頻繁,可以嘗試下面提到 -XX:+ExplicitGCInvokesConcurrent 引數 

====================================================================== 

2、-XX:+DisableExplicitGC 與 Remote Method Invocation (RMI) 與 -Dsun.rmi.dgc.{server|client}.gcInterval= 

看了上一個例子有沒有覺得-XX:+DisableExplicitGC引數用起來很危險?那乾脆完全不要用這個引數吧。又有什麼坑呢? 

前段時間有個應用的開發來抱怨,說某次升級JDK之前那應用的GC狀況都很好,很長時間都不會發生full GC,但升級後發現每一小時左右就會發生一次。經過對比發現,升級的同時也吧啟動引數改了,把原本有的-XX:+DisableExplicitGC給去掉了。 

觀察到的日誌有明顯特徵。一位同事表示: 
引用
線上機器出現一個場景;每隔1小時出現一次Full GC,用btrace看了一下呼叫地: 
who call system.gc : 
sun.misc.GC$Daemon.run(GC.java:92) 

預發機沒什麼流量,也會每一小時一次Full GC 
頻率正好是一小時一次 

Gc log程式碼  收藏程式碼
  1. 2011-09-23T10:49:38.071+0800327692.227: [Full GC (System) 327692.227: [CMS: 75793K->75759K(2097152K), 0.6923690 secs] 430298K->75759K(3984640K), [CMS Perm : 104136K->104124K(173932K)], 0.6925570 secs]  



實際上這裡在做的是分散式GC。Sun JDK的分散式GC是用純Java實現的,為RMI服務。 
RMI DGC相關引數的介紹文件:http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/sunrmiproperties.html 

(後面回頭再補…先睡覺去了) 

資料: 
jGuru: Distributed Garbage Collection 
http://mail.openjdk.java.net/pipermail/hotspot-dev/2011-December/004909.html 
Tony Printezis 寫道
RMI has a distributed GC that relies on reference processing to allow 
each node to recognize that some objects are unreachable so it can 
notify a remote node (or nodes) that some remote references to them do 
not exist any more. The remote node might then be able to reclaim 
objects that are only remotely reachable. (Or this is how I understood 
it at least.) 

RMI used to call System.gc() once a minute (!!!) but after some 
encouragement from yours truly they changed the default to once an hour 
(this is configurable using a property). Note that a STW Full GC is not 
really required as long as references are processed. So, in CMS (and 
G1), a concurrent cycle is fine which is why we recommend to use 
-XX:+ExplicitGCInvokesConcurrent in this case. 

I had been warned by the RMI folks against totally disabling those 
System.gc()'s (e.g., using -XX:+DisableExplicitGC) given that if Full 
GCs / concurrent cycles do not otherwise happen at a reasonable 
frequency then remote nodes might experience memory leaks since they 
will consider that some otherwise unreachable remote references are 
still live. I have no idea how severe such memory leaks would be. I 
guess they'd be very application-dependent. 

An additional thought that just occurred to me: instead of calling 
System.gc() every hour what RMI should really be doing is calling 
System.gc() every hour provided no old gen GC has taken place during the 
last hour. This would be relatively easy to implement by accessing the 
old GC counter through the GC MXBeans. 

Tony

再加倆連結:http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-January/004929.html 
http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-January/004946.html 

《Java Performance》的303和411頁正好也提到了-XX:+DisableExplicitGC與RMI之間的干擾的事情,有興趣可以讀一下,雖然只有一小段。 

====================================================================== 

3、-XX:+ExplicitGCInvokesConcurrent 或 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 

C++程式碼  收藏程式碼
  1. product(bool, ExplicitGCInvokesConcurrent, false,                  \  
  2.   "A System.gc() request invokes a concurrent collection;"         \  
  3.   " (effective only when UseConcMarkSweepGC)")                     \  
  4.                                    \  
  5. product(bool, ExplicitGCInvokesConcurrentAndUnloadsClasses, false, \  
  6.   "A System.gc() request invokes a concurrent collection and "     \  
  7.   "also unloads classes during such a concurrent gc cycle "        \  
  8.   "(effective only when UseConcMarkSweepGC)")                      \  


跟上面的第一個例子的-XX:+DisableExplicitGC一樣,這兩個引數也是用來改變System.gc()的預設行為用的;不同的是這兩個引數只能配合CMS使用(-XX:+UseConcMarkSweepGC),而且System.gc()還是會觸發GC的,只不過不是觸發一個完全stop-the-world的full GC,而是一次併發GC週期。 

CMS GC週期中也會做reference processing。所以如果用這兩個引數的其中一個,而不是用-XX:+DisableExplicitGC的話,就避開了由full GC帶來的長GC pause,同時NIO direct memory的OOM也不會那麼容易發生。 

做了個跟第一個例子類似的例子,在這裡:https://gist.github.com/1344251 

《Java Performance》的303頁有講到這倆引數。 

相關bug:6919638 CMS: ExplicitGCInvokesConcurrent misinteracts with gc locker 
<< JDK6u23修復了這個問題 

====================================================================== 

4、-XX:+GCLockerInvokesConcurrent 

C++程式碼  收藏程式碼
  1. product(bool, GCLockerInvokesConcurrent, false,        \  
  2.   "The exit of a JNI CS necessitating a scavenge also" \  
  3.   " kicks off a bkgrd concurrent collection")          \  


(內容回頭補…) 

====================================================================== 

5、MaxDirectMemorySize 與 NIO direct memory 的預設上限 

-XX:MaxDirectMemorySize 是用來配置NIO direct memory上限用的VM引數。 
C++程式碼  收藏程式碼
  1. product(intx, MaxDirectMemorySize, -1,                         \  
  2.         "Maximum total size of NIO direct-buffer allocations") \  

但如果不配置它的話,direct memory預設最多能申請多少記憶體呢?這個引數預設值是-1,顯然不是一個“有效值”。所以真正的預設值肯定是從別的地方來的。 

在Sun JDK 6和OpenJDK 6裡,有這樣一段程式碼,sun.misc.VM: 
Java程式碼  收藏程式碼
  1. // A user-settable upper limit on the maximum amount of allocatable direct  
  2. // buffer memory.  This value may be changed during VM initialization if  
  3. // "java" is launched with "-XX:MaxDirectMemorySize=<size>".  
  4. //  
  5. // The initial value of this field is arbitrary; during JRE initialization  
  6. // it will be reset to the value specified on the command line, if any,  
  7. // otherwise to Runtime.getRuntime().maxMemory().  
  8. //  
  9. private static long directMemory = 64 * 1024 * 1024;  
  10.   
  11. // If this method is invoked during VM initialization, it initializes the  
  12. // maximum amount of allocatable direct buffer memory (in bytes) from the  
  13. // system property sun.nio.MaxDirectMemorySize.  The system property will  
  14. // be removed when it is accessed.  
  15. //  
  16. // If this method is invoked after the VM is booted, it returns the  
  17. // maximum amount of allocatable direct buffer memory.  
  18. //  
  19. public static long maxDirectMemory() {  
  20.     if (booted)  
  21.         return directMemory;  
  22.   
  23.     Properties p = System.getProperties();  
  24.     String s = (String)p.remove("sun.nio.MaxDirectMemorySize");  
  25.     System.setProperties(p);  
  26.   
  27.     if (s != null) {  
  28.         if (s.equals("-1")) {  
  29.             // -XX:MaxDirectMemorySize not given, take default  
  30.             directMemory = Runtime.getRuntime().maxMemory();  
  31.         } else {  
  32.             long l = Long.parseLong(s);  
  33.             if (l > -1)  
  34.                 directMemory = l;  
  35.         }  
  36.     }  
  37.   
  38.     return directMemory;  
  39. }  

(程式碼裡原本的註釋有個寫錯的地方,上面有修正) 
當MaxDirectMemorySize引數沒被顯式設定時它的值就是-1,在Java類庫初始化時maxDirectMemory()被java.lang.System的靜態構造器呼叫,走的路徑就是這條: 
Java程式碼  收藏程式碼
  1. if (s.equals("-1")) {  
  2.     // -XX:MaxDirectMemorySize not given, take default  
  3.     directMemory = Runtime.getRuntime().maxMemory();  
  4. }  

而Runtime.maxMemory()在HotSpot VM裡的實現是: 
C++程式碼  收藏程式碼
  1. JVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void))  
  2.   JVMWrapper("JVM_MaxMemory");  
  3.   size_t n = Universe::heap()->max_capacity();  
  4.   return convert_size_t_to_jlong(n);  
  5. JVM_END  

這個max_capacity()實際返回的是 -Xmx減去一個survivor space的預留大小(G1除外)。 

結論:MaxDirectMemorySize沒顯式配置的時候,NIO direct memory可申請的空間的上限就是-Xmx減去一個survivor space的預留大小 
大家感興趣的話可以試試在不同的-Xmx的條件下不設定MaxDirectMemorySize,並且呼叫一下sun.misc.VM.maxDirectMemory()看得到的值的相關性。 

該行為在JDK7裡沒變,雖然具體實現的程式碼有些變化。請參考http://hg.openjdk.java.net/jdk7/jdk7/jdk/rev/b444f86c4abe 

====================================================================== 

6、-verbose:gc 與 -XX:+PrintGCDetails 

經常能看到在推薦的標準引數裡這兩個引數一起出現。實際上它們有啥關係? 

在Oracle/Sun JDK 6裡,"java"這個啟動程式遇到"-verbosegc"會將其轉換為"-verbose:gc",將啟動引數傳給HotSpot VM後,HotSpot VM遇到"-verbose:gc"則會當作"-XX:+PrintGC"來處理。 
也就是說 -verbosegc、-verbose:gc、-XX:+PrintGC 三者的作用是完全一樣的。 

而當HotSpot VM遇到 -XX:+PrintGCDetails 引數時,會順帶把 -XX:+PrintGC 給設定上。 
也就是說 -XX:+PrintGCDetails 包含 -XX:+PrintGC,進而也就包含 -verbose:gc。 

既然 -verbose:gc 都被包含了,何必在命令列引數裡顯式設定它呢? 

====================================================================== 

7、-XX:+UseFastEmptyMethods 與 -XX:+UseFastAccessorMethods 

雖然不常見,但偶爾也會見到推薦的標準引數上有這倆的身影。 

empty method顧名思義就是空方法,也就是方法體只包含一條return指令、返回值型別為void的Java方法。 
accessor method在這裡則有很具體的定義: 
C++程式碼  收藏程式碼
  1. bool methodOopDesc::is_accessor() const {  
  2.   if (code_size() != 5) return false;  
  3.   if (size_of_parameters() != 1) return false;  
  4.   if (java_code_at(0) != Bytecodes::_aload_0 ) return false;  
  5.   if (java_code_at(1) != Bytecodes::_getfield) return false;  
  6.   if (java_code_at(4) != Bytecodes::_areturn &&  
  7.       java_code_at(4) != Bytecodes::_ireturn ) return false;  
  8.   return true;  
  9. }  

如果從Java原始碼的角度來理解,accessor method就是形如這樣的: 
Java程式碼  收藏程式碼
  1. public class Foo {  
  2.   private int value;  
  3.     
  4.   public int getValue() {  
  5.     return this.value;  
  6.   }  
  7. }  

關鍵點是: 
1、必須是成員方法;靜態方法不行 
2、返回值型別必須是引用型別或者int,其它都不算 
3、方法體的程式碼必須滿足aload_0; getfield #index; areturn或ireturn這樣的模式。 
留意:方法名是什麼都沒關係,是不是get、is、has開頭都不重要。 

那麼這倆有啥問題? 

取自JDK 6 update 27: 
C++程式碼  收藏程式碼
  1. product(bool, UseFastEmptyMethods, true,                   \  
  2.         "Use fast method entry code for empty methods")    \  
  3.                                                            \  
  4. product(bool, UseFastAccessorMethods, true,                \  
  5.         "Use fast method entry code for accessor methods") \  

看到這倆引數的預設值都是true了麼?也就是說,在Oracle/Sun JDK 6上設定這引數其實也是沒意義的,跟預設一樣,一直到最新的JDK 6 update 29都是如此。 

不過在Oracle/Sun JDK 7裡,情況有變化。 
Bug ID: 6385687 UseFastEmptyMethods/UseFastAccessorMethods considered harmful 
上述bug對應的程式碼變更後,這倆引數的預設值改為了false。 

本來想多寫點這塊的…算,還是長話短說。 

Oracle JDK 7裡的HotSpot VM已經開始有比較好的多層編譯(tiered compilation)支援,可以預見在不久的將來該模式將成為HotSpot VM預設的執行模式。當前該模式尚未預設開啟;可以通過-XX:+TieredCompilation 來開啟。 

有趣的是,在使用多層編譯模式時,如果UseFastAccessorMethods/UseFastEmptyMethods是開著的,有些多型方法呼叫點的效能反而會顯著下降。所以,為了適應多層編譯模式,JDK 7裡這兩個引數的預設值就被改為false了。 
在郵件列表上有過相關討論:review for 6385687: UseFastEmptyMethods/UseFastAccessorMethods considered harmful 

====================================================================== 

8、-XX:+UseCMSCompactAtFullCollection 

這個引數在Oracle/Sun JDK 6裡一直都預設是true,完全沒必要顯式設定,設了也不會有啥不同的效果。 
C++程式碼  收藏程式碼
  1. product(bool, UseCMSCompactAtFullCollection, true,    \  
  2.         "Use mark sweep compact at full collections") \  

我不認為顯式設定一個跟預設值相同的引數有什麼維護上的好處。要維護的引數多了反而更容易成為維護的噩夢吧。後面的人會不知道到底當初為什麼要設定這個引數。 

相關的有個 CMSFullGCsBeforeCompaction 引數,請參考另一帖裡的討論:http://hllvm.group.iteye.com/group/topic/28854#209294 

同樣,在Oracle/Sun JDK 6和OpenJDK 6裡,CMSParallelRemarkEnabled 也一直預設是true,沒必要顯式設定-XX:+CMSParallelRemarkEnabled。 
有很多bool型別的引數預設都是true,顯式設定它們之前最好先用這帖開頭介紹的辦法看看預設值是否已經是想要的值了。 

C++程式碼  收藏程式碼
  1. product(bool, CMSScavengeBeforeRemark, false,          \  
  2.         "Attempt scavenge before the CMS remark step") \  

這個預設倒是false。如果一個應用統計到的young GC時間都比較短而CMS remark的時間比較長,那麼可以試試開啟這個引數,在做remark之前先做一次young GC。是否能有效縮短remark的時間視應用情況而異,所以開這個引數的話請一定做好測試。 

====================================================================== 

9、-XX:CMSMaxAbortablePrecleanTime=5000 

同上…預設就是5000 
C++程式碼  收藏程式碼
  1. product(intx, CMSMaxAbortablePrecleanTime, 5000,    \  
  2.         "(Temporary, subject to experimentation)"   \  
  3.         "Maximum time in abortable preclean in ms") \  

還是不要設跟預設值一樣的引數了吧。 

====================================================================== 

10、-Xss 與 -XX:ThreadStackSize 

參考我之前發過的兩帖: 
What the difference between -Xss and -XX:ThreadStackSize is? 
Inconsistency between -Xss and -XX:ThreadStackSize in the java launcher 

(詳情回頭補~) 

====================================================================== 

11、-Xmn 與 -XX:NewSize、-XX:MaxNewSize 

如果同時設定了-XX:NewSize與-XX:MaxNewSize遇到“Could not reserve enough space for object heap”錯誤的話,請看看是不是這帖所說的問題。早期JDK 6似乎都受這問題影響,一直到JDK 6 update 14才修復。 

====================================================================== 

12、-Xmn 與 -XX:NewRatio 

====================================================================== 

13、-XX:NewRatio 與 -XX:NewSize、-XX:OldSize 

====================================================================== 

14、jmap -heap看到的引數值與實際起作用的引數的關係? 

發了幾個例子在這裡:https://gist.github.com/1363195 
其中有個看起來很恐怖的值: 
Java程式碼  收藏程式碼
  1. MaxNewSize       = 17592186044415 MB  

這是啥來的? 

C++程式碼  收藏程式碼
  1. product(uintx, MaxNewSize, max_uintx,                                  \  
  2.         "Maximum new generation size (in bytes), max_uintx means set " \  
  3.         "ergonomically")     


在HotSpot VM裡,intx是跟平臺字長一樣寬的帶符號整型,uintx是其無符號版。 
max_uintx是(uintx) -1,也就是說在32位平臺上是無符號的0xFFFFFFFF,64位平臺上則是0xFFFFFFFFFFFFFFFF。 

jmap -heap顯示的部分引數是以MB為單位來顯示的,而MaxNewSize的單位是byte。我跑例子的平臺是64位的,於是算一下 0xFFFFFFFFFFFFFFFF / 1024 / 1024 = 17592186044415 MB 。 

引數的說明告訴我們,當MaxNewSize的值等於max_uintx時,意思就是交由ergonomics來自動選擇young gen的最大大小。並不是說young gen的最大大小真的有0xFFFFFFFFFFFFFFFF這麼大。 

要注意的是,HotSpot VM有大量可調節的引數,並不是所有引數在某次執行的時候都有效。 

例如說設定了-Xmn的話,NewRatio就沒作用了。 
又例如說, 
C++程式碼  收藏程式碼
  1. product(uintx, OldSize, ScaleForWordSize(4*M),        \  
  2.         "Initial tenured generation size (in bytes)") \  

-XX:OldSize引數的預設值在32位平臺上是4M,在64位平臺上是5M多。但如果這個引數沒有被顯式設定過,那它實際上是沒作用的;old gen的大小會通過Java heap的整體大小與young gen的大小配置計算出來,但OldSize引數卻沒有被更新(因為根本沒用它)。於是這個引數的值與實際執行的狀況就可能會不相符。 
一種例外的情況是,如果-Xmx非常小,比NewSize+OldSize的預設值還小,那這個OldSize的預設值就會起作用,把MaxHeapSize給撐大。 
C++程式碼  收藏程式碼
  1. void TwoGenerationCollectorPolicy::initialize_flags() {  
  2.   GenCollectorPolicy::initialize_flags();  
  3.   
  4.   OldSize = align_size_down(OldSize, min_alignment());  
  5.   if (NewSize + OldSize > MaxHeapSize) {  
  6.     MaxHeapSize = NewSize + OldSize;  
  7.   }  
  8.   MaxHeapSize = align_size_up(MaxHeapSize, max_alignment());  
  9. //...  
  10. }  

可以看這邊的一個例子:https://gist.github.com/1375782 

====================================================================== 

15、-XX:+AlwaysTenure、-XX:+NeverTenure、-XX:MaxTenuringThreshold=0 或 "-XX:MaxTenuringThreshold=markOopDesc::max_age + 1" 

ParNew的時候,設定-XX:+AlwaysTenure隱含-XX:MaxTenuringThreshold=0;不過-XX:+NeverTenure卻沒啥特別的作用。 

-XX:+AlwaysTenure 與 -XX:+NeverTenure 是互斥的,最後一個出現的那個會同時決定這兩個引數的值。 

====================================================================== 

16、-XX:MaxTenuringThreshold 的預設值? 

C++程式碼  收藏程式碼
  1. product(intx, MaxTenuringThreshold,    15, \  
  2.   "Maximum value for tenuring threshold")  \  

Oracle/Sun JDK 6中,選擇CMS之外的GC時,MaxTenuringThreshold(以下簡稱MTT)的預設值是15;而選擇了CMS的時候,MTT的預設值是4而不是15。設定是在 Arguments::set_cms_and_parnew_gc_flags() 裡做的。 

在Sun JDK 6之前(1.4.2、5),選擇CMS的時候MTT的預設值則是0,也就是等於設定了-XX:+AlwaysTenure——所有eden裡的活物件在經歷第一次minor GC的時候就會直接晉升到old gen,而survivor space直接就沒用了。 

Command prompt程式碼  收藏程式碼
  1. $ java -version  
  2. java version "1.6.0_25"  
  3. Java(TM) SE Runtime Environment (build 1.6.0_25-b06)  
  4. Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)  
  5. $ java -XX:+PrintFlagsFinal | egrep 'MaxTenuringThreshold|UseParallelGC|UseConcMarkSweepGC'  
  6.      intx MaxTenuringThreshold                      = 15              {product}             
  7.      bool UseConcMarkSweepGC                        = false           {product}             
  8.      bool UseParallelGC                            := true            {product}             
  9. $ java -XX:+PrintFlagsFinal -XX:+UseConcMarkSweepGC | egrep 'MaxTenuringThreshold|UseParallelGC|UseConcMarkSweepGC'  
  10.      intx MaxTenuringThreshold                     := 4               {product}             
  11.      bool UseConcMarkSweepGC                       := true            {product}             
  12.      bool UseParallelGC                             = false           {product}             


====================================================================== 

17、-XX:+CMSClassUnloadingEnabled 

CMS remark暫停時間會增加,所以如果類載入並不頻繁、String的intern也沒有大量使用的話,這個引數還是不開的好。 

====================================================================== 

18、-XX:+AggressiveHeap 

====================================================================== 

19、-XX:+UseCompressedOops 有益?有害? 

先把微博上回復別人問題的解答放這邊。 

本來如果功能沒bug的話,Oracle/Sun JDK 6的64位HotSpot上,GC堆在26G以下(-Xmx + -XX:MaxPermSize)的時候用多數都是有益的。 
開啟壓縮指標後,從程式碼路徑(code path)和CPI(cycles per instruction)兩個角度看,情況是不一樣的: 
·開啟壓縮指標會使程式碼路徑變長,因為所有在GC堆裡的、指向GC堆內物件的指標都會被壓縮,這些指標的訪問就需要更多的程式碼才可以實現。不要以為只是讀寫欄位才受影響,其實例項方法呼叫、子型別檢查等操作也受影響——“klass”也是一個指標,也被壓縮了。 
·但從CPI的角度看,由於壓縮指標使需要拷貝的資料量變小了,cache miss的機率隨之降低,結果CPI可能會比壓縮前降低。綜合來看,開了壓縮指標通常能大幅降低GC堆記憶體的消耗,同時維持或略提高Java程式的速度。 

但,JDK6u23之前那個引數的bug實在太多,最好別用;而6u23之後它就由ergonomics自動開啟了,不用自己設。如果在6u23或更高版本碰到壓縮指標造成的問題的話,顯式設定 -XX:-UseCompressedOops 。 

我能做的建議是如果在64位Oracle/Sun JDK 6/7上,那個引數不要顯式設定。 

關於HotSpot VM的ergonomics自動開啟壓縮指標功能,請參考之前的一帖。 

有些庫比較“聰明”,會自行讀取VM引數來調整自己的一些引數,例如Berkeley DB Java Edition。但這些庫實現得不好的時候反而會帶來一些麻煩:BDB JE要求顯式指定-XX:+UseCompressedOops才能有效的調整它的快取大小。所以在用BDB JE並且Java堆+PermGen大小小於32GB的時候,請顯式指定-XX:+UseCompressedOops吧。參考Warning on Compressed Oops 

====================================================================== 

20、-XX:LargePageSizeInBytes=128m ? 

或者是 -XX:LargePageSizeInBytes=256m ? 
其實這個引數的值是多少不是問題,問題是這個引數到底有沒有起作用。 

或許有人讀過很老的調優建議資料,例如這個: 
(2005) Java Tuning White Paper - 4.2.3 Tuning Example 3: Try 256 MB pages 
或者是別的一些內容很老的資料。它們提到了-XX:LargePageSizeInBytes=引數。這些老資料也沒說錯,在Sun JDK 5裡 -XX:LargePageSizeInBytes= 引數只在Solaris上有效,使用的時候沒有別的引數保護。 

但是,實際上這個引數在Oracle/Sun JDK 6裡不配合-XX:+UseLargePages的話是不會起任何作用的。 

JDK 6裡的JVM的Linux版上初始化large page的地方: 
C++程式碼  收藏程式碼
  1. bool os::large_page_init() {  
  2.   if (!UseLargePages) return false;  
  3.   
  4.   if (LargePageSizeInBytes) {  
  5.     _large_page_size = LargePageSizeInBytes;  
  6.   } else {  
  7.     // ...  
  8.   }  
  9.     
  10.   // ...  
  11.   
  12.   // Large page support is available on 2.6 or newer kernel, some vendors  
  13.   // (e.g. Redhat) have backported it to their 2.4 based distributions.  
  14.   // We optimistically assume the support is available. If later it turns out  
  15.   // not true, VM will automatically switch to use regular page size.  
  16.   return true;  
  17. }  

看到了麼,沒有將UseLargePages設定為true的話,LargePageSizeInBytes根本沒機會被用上。

對應的,Solaris版: 
C++程式碼  收藏程式碼
  1. bool os::large_page_init() {  
  2.   if (!UseLargePages) {  
  3.     UseISM = false;  
  4.     UseMPSS = false;  
  5.     return false;  
  6.   }  
  7.   
  8.   // ...  
  9. }  


以及Windows版: 
C++程式碼  收藏程式碼
  1. bool os::large_page_init() {  
  2.   if (!UseLargePages) return false;  
  3.   
  4.   // ...  
  5. }  


在Oracle/Sun JDK 6以及Oracle JDK 7上要使用 -XX:LargePageSizeInBytes= 的話,請務必也設定上 -XX:+UseLargePages 。使用這兩個引數之前最好先確認作業系統是否真的只是large pages;作業系統不支援的話,設定這兩個引數也沒作用,只會退回到使用regular pages而已。 

====================================================================== 

21、-XX:+AlwaysPreTouch 

會把commit的空間跑迴圈賦值為0以達到“pretouch”的目的。開這個引數會增加VM初始化時的開銷,但後面涉及虛擬記憶體的開銷可能降低。 

在另一個討論帖裡有講該引數:http://hllvm.group.iteye.com/group/topic/28839#209144 

====================================================================== 

22、-XX:+UseTLAB 與 Runtime.freeMemory() 

====================================================================== 

23、-XX:+ParallelRefProcEnabled 

這個功能可以加速reference processing,但在JDK6u25和6u26上不要使用,有bug: 
Bug ID 7028845: CMS: 6984287 broke parallel reference processing in CMS 

====================================================================== 

24、-XX:+UseConcMarkSweepGC 與 -XX:+UseAdaptiveSizePolicy 

這兩個選項在現有的Oracle/Sun JDK 6和Oracle JDK 7上都不要搭配在一起使用——CMS用的adaptive size policy還沒實現完,用的話可能會crash。 

目前HotSpot VM上只有ParallelScavenge系的GC才可以配合-XX:+UseAdaptiveSizePolicy使用;也就是隻有-XX:+UseParallelGC或者-XX:+UseParallelOldGC。Jon Masamitsu在郵件列表上提到過。 

題外話:開著UseAdaptiveSizePolicy的ParallelScavenge會動態調整各空間的大小,有可能會造成兩個survivor space的大小被調整得不一樣大。Jon Masamitsu在這封郵件裡解釋了原因。 

追加:JDK9裡CMS終於要徹底不支援adaptive size policy了:https://bugs.openjdk.java.net/browse/JDK-8034246 

====================================================================== 

25、-XX:+UseAdaptiveGCBoundary 

JDK 6裡不要用這個選項,有bug。 

====================================================================== 

26、-XX:HeapDumpPath 與 -XX:+HeapDumpBeforeFullGC、-XX:+HeapDumpAfterFullGC、-XX:+HeapDumpOnOutOfMemoryError 

-XX:+HeapDumpBeforeFullGC、-XX:+HeapDumpAfterFullGC、-XX:+HeapDumpOnOutOfMemoryError 這幾個引數可以在不同條件下做出HPROF格式的heap dump。但很多人都會疑惑:做出來的heap dump存到哪裡去了? 

如果不想費神去摸索到底各種環境被配置成什麼樣、“working directory”到底在哪裡的話,就在VM啟動引數里加上 -XX:HeapDumpPath=一個絕對路徑 吧。這樣,自動做出的heap dump就會被存到指定的目錄裡去。 
當然相對路徑也支援,不過用了相對路徑就又得弄清楚當前的“working directory”在哪裡了。 

====================================================================== 

26、UseDepthFirstScavengeOrder 

以前有過這樣一個引數可以設定young gen遍歷物件圖的順序,深度還是廣度優先不過高於JDK 6 update 22就沒用了,ParallelScavenge變為只用深度優先而不用廣度優先。 
具體的changeset在這裡:http://hg.openjdk.java.net/hsx/hotspot-main/hotspot/rev/9d7a8ab3736b 

HotSpot VM裡的arguments.cpp檔案裡有obsolete_jvm_flags陣列,那邊宣告的引數都要留意是已經沒用的。 

OpenJDK郵件列表上有個有趣的討論跟掃描順序相關:Parallel GC and array object layout: way off the base and laid out in reverse? 

====================================================================== 

-XX:CMSScheduleRemarkEdenPenetration=0 
Schedule the remark pause immediately after the 
next young collection. 
-XX:CMSScheduleRemarkEdenSizeThreshold=0 
Any sized eden should allow scheduling of the remark 
pause.  That is, no eden is too small to schedule. 
-XX:CMSMaxAbortablePrecleanTime=30000 

Wait up to 30 seconds for the remark to be scheduled 
after a young collection.  Otherwise, wait only up to 

the default of 5 seconds. 

轉載:http://hllvm.group.iteye.com/group/topic/27945

相關文章