JVM 引數設定(JDK1.6)
前言
本文是對 JVM flag系列文章 的翻譯和精簡
- JDK 作者是基於JDK6的,本人為JDK8.
- 示例程式碼 命令列以 $ 開頭的為複製原作者,*λ *為本人實測
一 純解釋、純編譯還是混合
版本資訊
-version
λ java -version java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
列印版本資訊
-showversion
加強版的version,列印版本資訊並執行後續任務
λ java -showversion -jar myjar.jar java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode) ._______ _ _ /\\ / ___'_ __ _ _(_)_ ____ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/___)| |_)| | | | | || (_| |) ) ) ) '|____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot ::(v1.4.2.RELEASE)
解釋、編譯還是混合?
-Xint
強制bytecode在解釋模式執行,可以節省一點點編譯時間但是執行極其緩慢(至少10倍)
-Xcomp
強制bytecode編譯為原生程式碼後執行,編譯時間稍長,但是執行效率有部分提升.最大的缺點是會導致JIT無法生效(JIT需要在執行時發現熱點(hotspot)程式碼,並執行 大量優化 ,JIT是hotspot vm的關鍵,也正是JIT的各種神優化才使java慢慢脫離了執行緩慢的非議)
-Xmixed
hotspot的預設配置, 對於熱點程式碼優化,對於極少執行的程式碼以解釋方式執行,在編譯時間和執行效率取得平衡
二 標誌分類和詳細編譯列印
JVM的標誌分類
-abc
JVM標準標誌.基本不會發生改變 如 -server -version
-Xabc
JVM實驗性質標誌,未來可能發生改變,雖然沒有保障但仍然較為穩定,使用 java -X 顯示標誌列表(會有遺漏)
λ java -X -Xmixed混合模式執行 (預設) -Xint僅解釋模式執行 -Xbootclasspath:<用 ; 分隔的目錄和 zip/jar 檔案> 設定搜尋路徑以引導類和資源 -Xbootclasspath/a:<用 ; 分隔的目錄和 zip/jar 檔案> 附加在引導類路徑末尾 -Xbootclasspath/p:<用 ; 分隔的目錄和 zip/jar 檔案> 置於引導類路徑之前 -Xdiag顯示附加診斷訊息 -Xnoclassgc禁用類垃圾收集 -Xincgc啟用增量垃圾收集 -Xloggc:<file>將 GC 狀態記錄在檔案中 (帶時間戳) -Xbatch禁用後臺編譯 -Xms<size>設定初始 Java 堆大小 -Xmx<size>設定最大 Java 堆大小 -Xss<size>設定 Java 執行緒堆疊大小 -Xprof輸出 cpu 配置檔案資料 -Xfuture啟用最嚴格的檢查, 預期將來的預設值 -Xrs減少 Java/VM 對作業系統訊號的使用 (請參閱文件) -Xcheck:jni對 JNI 函式執行其他檢查 -Xshare:off不嘗試使用共享類資料 -Xshare:auto在可能的情況下使用共享類資料 (預設) -Xshare:on要求使用共享類資料, 否則將失敗。 -XshowSettings顯示所有設定並繼續 -XshowSettings:all 顯示所有設定並繼續 -XshowSettings:vm 顯示所有與 vm 相關的設定並繼續 -XshowSettings:properties 顯示所有屬性設定並繼續 -XshowSettings:locale 顯示所有與區域設定相關的設定並繼續 -X 選項是非標準選項, 如有更改, 恕不另行通知。
-XXabc
JVM實驗性質標誌,未來很可能發生改變,不太穩定,但很少變化
-XX 的特殊語法
-XX:+<name>
僅對boolean標誌,開啟選項,相應的 -XX:-<name> 關閉選項
-XX:<name>=<value>
設定選項
顯示JIT編譯資訊
-XX:+PrintCompilation
在程式執行時顯示JIT的編譯資訊
$ java -server -XX:+PrintCompilation Benchmark 1java.lang.String::hashCode (64 bytes) 2java.lang.AbstractStringBuilder::stringSizeOfInt (21 bytes) 3java.lang.Integer::getChars (131 bytes) 4java.lang.Object::<init> (1 bytes) ---njava.lang.System::arraycopy (static) 5java.util.HashMap::indexFor (6 bytes) 6java.lang.Math::min (11 bytes) 7java.lang.String::getChars (66 bytes) 8java.lang.AbstractStringBuilder::append (60 bytes) ...
-XX:+CITime
在程式執行時顯示JIT編譯時間
$ java -server -XX:+CITime Benchmark [...] Accumulated compiler times (for compiled methods only) ------------------------------------------------ Total compilation time:0.178 s Standard compilation:0.129 s, Average : 0.004 On stack replacement:0.049 s, Average : 0.024 [...]
比較編譯時間
拿公司的jar跑的,沒跑起來,結果並不準確
- -Xcomp
Accumulated compiler times (for compiled methods only) ------------------------------------------------ Total compilation time: 45.480 s Standard compilation: 44.531 s, Average : 0.001 On stack replacement:0.949 s, Average : 0.045 Detailed C1 Timings Setup time:0.000 s ( 0.0%) Build IR:3.282 s (37.8%) Optimize:0.186 s ( 2.1%) RCE:0.038 s ( 0.4%) Emit LIR:2.887 s (33.2%) LIR Gen:0.633 s ( 7.3%) Linear Scan:2.226 s (25.6%) LIR Schedule:0.000 s ( 0.0%) Code Emission:0.974 s (11.2%) Code Installation:1.543 s (17.8%) Instruction Nodes: 2332355 nodes
- -Xint
Accumulated compiler times (for compiled methods only) ------------------------------------------------ Total compilation time:0.000 s Standard compilation:0.000 s, Average : -1.#IO On stack replacement:0.000 s, Average : -1.#IO Total compiled methods:0 methods Standard compilation:0 methods On stack replacement:0 methods Total compiled bytecodes :0 bytes Standard compilation:0 bytes On stack replacement:0 bytes Average compilation speed: -2147483648 bytes/s nmethod code size:0 bytes nmethod total size:0 bytes
3 -Xmixed
Accumulated compiler times (for compiled methods only) ------------------------------------------------ Total compilation time: 18.903 s Standard compilation: 17.893 s, Average : 0.004 On stack replacement:1.011 s, Average : 0.014 Detailed C1 Timings Setup time:0.000 s ( 0.0%) Build IR:0.554 s (43.9%) Optimize:0.033 s ( 2.6%) RCE:0.007 s ( 0.6%) Emit LIR:0.438 s (34.7%) LIR Gen:0.095 s ( 7.5%) Linear Scan:0.339 s (26.8%) LIR Schedule:0.000 s ( 0.0%) Code Emission:0.141 s (11.2%) Code Installation:0.128 s (10.2%) Instruction Nodes: 345057 nodes Total compiled methods:4749 methods Standard compilation:4679 methods On stack replacement:70 methods Total compiled bytecodes : 959126 bytes Standard compilation: 921785 bytes On stack replacement:37341 bytes Average compilation speed:50738 bytes/s nmethod code size: 8556928 bytes nmethod total size: 16044616 bytes
可以看出-Xint完全沒有編譯,即使結果不準確也很容易理解-Xmixed模式的編譯時間會比-Xcomp短
解鎖JVM 實驗性質標誌
-XX:+UnlockExperimentalVMOptions
解鎖JVM實驗性質標誌.
如果你設定了某個-XX標誌,但是程式啟動後立刻輸出 Unrecognized VM option ,一般是拼寫錯誤.如果 Error: VM option 'Xxxx' is experience and must be enabled via -XX:+UnlockXXXVMOptions. 是這可能是JVM的安全策略,類似於二次確認,某些標誌可能會帶來意向不到的問題比如生成大量日誌,所以在遇到這個問題後最好再去確定下標誌的作用,比如下面這個例子
λjava-XX:+LogCompilation -jar myjar.jar Error: VM option 'LogCompilation' is diagnostic and must be enabled via -XX:+UnlockDiagnosticVMOptions. Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit.
另外有些標誌是為jvm開發人員提供的,需要debug build的jvm才能使用,如果在stable build中使用會有相應的錯誤提示
更詳細的編譯資訊
-XX:+LogCompilation
將更詳細的編譯資訊(編譯執行緒及編譯的方法)儲存到hotspot.log檔案,需要新增UnockDiagnostic標誌
-XX:+PrintOptoAssembly
顯示並儲存編譯出的native code到hotspot.log
在debug build才能使用
λjava-XX:+PrintOptoAssembly -jar myjar.jar Error: VM option 'PrintOptoAssembly' is notproduct and is available only in debug version of VM. Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit.
三
列印-XX標誌
-XX:+PrintFlagsFinal
列印所有生效的-XX標誌,如果沒有手動設定會顯示預設設定
λjava-XX:+PrintFlagsFinal -jar kgamebox-web-service.jar [Global flags] uintx AdaptiveSizeThroughPutPolicy= 0{product} uintx AdaptiveTimeWeight= 25{product} bool AdjustConcurrency= false{product} bool AggressiveOpts= false{product} intx AliasLevel= 3{C2 product} bool AlignVector= true{C2 product} intx AllocateInstancePrefetchLines= 1{product} intx AllocatePrefetchDistance= 256{product} intx AllocatePrefetchInstr= 3{product} intx AllocatePrefetchLines= 3{product} intx AllocatePrefetchStepSize= 64{product} intx AllocatePrefetchStyle= 1{product} bool AllowJNIEnvProxy= false{product}
格式為
值型別 名稱 [:]= 值 環境型別
帶有 : 表示被手動覆蓋(JVM智慧分析或使用者手動設定), 只有 = 為預設值
-XX:+PrintFlagsInitial
與上面類似,不過即使手動設定了也只顯示預設值,
額外顯示實現和分析相關標誌
新增 -XX:+UnlockExperimentalVMOptions 和 -XX:+UnlockDiagnosticVMOptions
- 對於 Final,未新增時為723,新增後為867
- 對於Initial沒有影響
統計檔案行數可使用 wc -l filename
只顯示手動設定標誌
-XX:+PrintCommandLineFlags
只顯示使用者設定或JVM智慧設定的標誌(也就是上面的 := )
λjava-XX:+PrintCommandLineFlagsmyjar.jar -XX:InitialHeapSize=266699648 -XX:MaxHeapSize=4267194368 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
(這裡作者推薦將這個選項新增到jvm預設配置裡,這樣有利於jvm調優)
四 記憶體調優
JVM中共享記憶體分為堆區和方法區
對於G1之前的garbage collector
- 堆區按照如下方式劃分
- 新生代,包含新建立/朝生夕死的物件
- 老年代,經歷過一定次數GC仍然存活的新生代物件會移動到老年代
- 方法區也稱,永久代,包含類資訊,常量池等
對於G1,永久代被移除,變為metaSpace區,metaSpace區不再佔用JVM中的記憶體,而是使用本地堆記憶體(native heap),常量池也移動到堆區(JDK7),新生代和老年代的概念保留,但是已經沒有物理界限
記憶體設定
-Xms128(k|m|g) (or: -XX:InitialHeapSize)
設定堆記憶體大小為128k/m/g,ms(memory size) 總記憶體大小,一般表示堆記憶體下限
-Xms128(k|m|g) (or:-XX:MaxHeapSize)
堆記憶體上限,JVM的執行中會動態調整總記憶體,但是確保不會超過上限
值得注意的是如果要檢視啟動時設定的堆記憶體,應該查詢InitialHeapSize和MaxHeapSize這兩個欄位,而非Xms或Xmx
堆記憶體溢位自動儲存heap dump檔案
-XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpPath
堆記憶體溢位時,預設在jvm啟動目錄生成dump檔案,通過設定 -XX:+HeapDumpPath 可自定義儲存位置,檔名為 java_pid<pid>.hprof
由於堆記憶體溢位時dump檔案通常會很大,所以推薦設定自定義儲存位置
堆記憶體溢位時執行自定命令
-XX:OnOutOfMemoryError
使用示例如下
$ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError ="sh ~/cleanup.sh" MyApp
設定永久代記憶體(僅對G1之前的)
-XXPermSize -XX:MaxPermSize
設定永久代記憶體大小, 永久代是不佔用堆記憶體的,堆記憶體和永久代記憶體是不相關的,但是這兩塊記憶體都是由JVM管理的,而JDK8的metaSpace使用的direct memory是由計算機管理而非JVM
native code快取區
JVM有一塊記憶體區域用來儲存編譯後的native code,如果這塊記憶體滿了,不會再有任何編譯行為發生,也就是說JIT被停止了,現有的未編譯程式碼會以解釋模式執行,這會導致嚴重的效能問題
-XX:InitialCodeCacheSize -XX:ReservedCodeCacheSize
設定初始程式碼快取區大小 設定最大程式碼快取區大小
-XX:+UseCodeCacheFlushing
在快取區滿時釋放部分快取,可以避免快取區滿時剩餘程式碼全部以解釋模式執行的問題
五 新生代GC
新生代採用複製演算法(參見另一篇文章)
新生代分為1個eden和兩個survivor,兩個survivor只會使用一個,每次eden區快滿時會發生一次 minor gc ,將eden和正在使用的survivor中存活的物件複製到另一個存活的survivor中.如果一個物件存活超過一定次數minor gc就會晉升到老年代

新生代GC
minor gc vs major gc vs full gc
我對這三種gc也是傻傻分不清,網上查到的資料也不是太全,下面根據自己的理解分析一下,如果錯誤請指正,下面只是記憶體快滿導致的gc:
- minor gc 新生代快滿時會觸發minor gc,具體流程如上
- major gc 在觸發minor gc後,會有部分物件晉升到老年代,如果這時老年代快滿則會觸發major gc.
- full gc 對於jdk8之前,如果永久代快滿,會導致full gc
設定新生代記憶體
-XX:NewSize -XX:MaxNewSize
類似於 -Xms 和 -Xmx ,因為堆記憶體由新生代和老年代組成,推薦兩者的最大記憶體相同
設定新生代和老年代的記憶體佔比
-XX:NewRatio
//新生代:老年代=1:3,堆記憶體新生代佔1/4,老年代佔3/4 -XX:NewRatio=3
如果同時設定了固定範圍和比率,固定範圍擁有更高的優先順序,考慮如下配置
$ java -XX:NewSize=32m -XX:MaxNewSize=512m -XX:NewRatio=3 MyApp
初始時JVM會盡量設計新生代和老年代的比例為1:3,但是無論如何新生代記憶體大於32m小於512m
設計eden和survivor的記憶體佔比
-XX:SurvivorRatio
//eden:survivor=10:1,由於survivor是有兩塊的,所有新生代的記憶體eden佔比10/12,兩個survivor各佔1/12 -XX:SurvivorRatio=10
eden和survivor的失衡可能會導致如下兩種我們不期望發生的現象
- eden很大
由於eden很大,可以容納很多物件,minorGC的頻率就會降低,如果分配的物件大都是朝生夕死的,進行minorGC時雖然survivor很小但仍然可以容納存活下來的少數物件,這是理想狀態, 但是如果大多數物件不是朝生夕死的 ,由於survivor不能容納這麼多存活物件,這些物件只能被移動到老年代,且不論移動過程的耗費,這還會大大提高majorGC發生的機率,而majorGC的耗費遠高於minorGC. - eden很小
只能容納少量物件,minorGC發生的機率會增大.
survivor和晉升年齡調優
-XX:+PrintTenuringDistribution
列印survivor中物件,年齡資訊及晉升年齡
Desired survivor size 75497472 bytes, new threshold 15 (max 15) - age1:19321624 bytes,19321624 total - age2:79376 bytes,19401000 total - age3:2904256 bytes,22305256 total
以上資訊為: survivor的期望容量(並非survivor總容量) 約75MB, 當前晉升年齡 為15(當物件年齡大於改閾值就會晉升到老年代), 最大晉升年齡 也是15
- age1:19321624 bytes,19321624 total
年齡為 1 的物件約為 19MB , 小於等於該年齡的物件 約為19MB
現在假設進行了一個minorGC,GC後如下
Desired survivor size 75497472 bytes, new threshold 2 (max 15) - age1:68407384 bytes,68407384 total - age2:12494576 bytes,80901960 total - age3:79376 bytes,80981336 total - age4:2904256 bytes,83885592 total
可以看到年齡為4的物件約為3M,和GC前年齡為3的大小相同,說明之前年齡為3的物件全部存活,年齡為3的同理.
同時目前的survivor實際總容量約為83MB,超過了survivor的期望容量,因此 當前晉升年齡 調整為2,下次GC時年齡大於2的物件將晉升到老年代.
survivor相關設定
-XX:InitialTenuringThreshold, -XX:MaxTenuringThreshold
初始晉升年齡和最大晉升年齡
-XX:TargetSurvivorRatio
設定survivor的期望容量佔survivor總容量的比例,如 -XX:TargetSurvivorRatio=90 說明survivor期望容量為總容量的90%
僅在測試時使用的兩個標誌
-XX:+NeverTenure -XX:+AlwaysTenure
顧名思義 永不晉升和總是晉升
六 更高的吞吐還是更短的停頓?
GC演算法是否優越,主要由下面兩個要素決定
- 高吞吐
- 低停頓
GC由幾個專用的GC執行緒負責,發生GC時,GC執行緒會與業務執行緒爭搶時間片,因此cpu不可能總是在執行業務執行緒,總有一部分時間是歸gc執行緒的.
吞吐量
業務執行緒執行總時間佔cpu執行總時間的比例就是吞吐量,例如cpu運行了100s,在此期間業務執行緒運行了99s.那吞吐量就是99%.
停頓時間
gc時會暫停所有業務執行緒,具體原因參見Stop the world.這裡業務執行緒的暫停時間就是停頓時間.
吞吐vs停頓
魚與熊掌不可兼得,高吞吐和低停頓是兩個相反的目標:
- 高吞吐要求gc儘量少
- 低延遲要求gc儘量多
值得注意的是,通過併發設計(併發和並行是有區別的,推薦大家研究一下,我對這兩個概念還是有點分不清,等講究清楚再分享),G1收集器正在儘量達成高吞吐和低停頓.
高吞吐的設計
gc期間需要確保堆中物件的狀態不能發生變化,因此工作執行緒在gc期間必須停止,也稱為stop the world.另外
- gc前要進行一系列預備工作
- gc執行緒切換需要耗費資源
gc最好在無法避免是才進行比如堆中無法容納更多物件時,相比高頻次的gc,節省了大量預備工作時間和執行緒切換時間.並以此達到高吞吐.
低延遲的設計
上面高吞吐的設計,因為堆中有大量物件需要處理,停頓時間不可避免的很長,為了達到低延遲,gc要儘可能的頻繁,這樣每次只需要回收少量物件,停頓時間也會大大減少.
面向高吞吐
下面幾個標誌都是指定gc收集器,具體參見gc收集器
- -XX:+UseSerialGC 新生代和老年代都使用單執行緒收集器
- -XX:+UseParallelGC JDK1.6表示新生代使用多執行緒收集器,在JDK1.7中作用與-XX:+UseParallelOldGC相同
- -XX:+UseParallelOldGC 新生代和老年代都是用多執行緒收集器
設定parallelGC執行緒數量
-XX:ParallelGCThreads
設定ParallelGC執行緒數量,如果沒有設定會按照以下規則進行(JDK1.6),獲取cpu核心數N
- 如果N<=8 GC執行緒數就為8
- 如果N>8 GC執行緒數為 3+5N/8
關閉JVM智慧調整功能
-XX:-UseAdaptiveSizePolicy
JDK1.5為JVM引入了稱為 ergonomics 的機制,在執行時,如果有證明修改某些設定能夠提高效能,ergonomics就會進行動態調整.它是 預設開啟 的,個人認為一般沒有理由去關閉它.
吞吐量設定
-XX:GCTimeRatio
-XX:GCTimeRatio=9表示工作時間佔總執行時間的9/10.預設為 99 ,即吞吐量為99%
最大停頓毫秒設定
-XX:MaxGCPauseMillis
也是根據此值在執行是進行分析並調整GC設定.對於新生代和老年代是分別處理的.並且最大停頓比吞吐量有更高的優先順序
七 CMS收集器
CMS 簡介
CMS設定
-XX:+UseConcMarkSweepGC
指定老年代使用CMS
-XX:+UseParNewGC
指定新生代為並行收集,當設定了-XX:+UseConcMarkSweepGC後自動生效,記得之前的 -XX:+UseParallelGC 嗎,為什麼不復用這個標誌呢? 對於CMS,新生代的並行gc採用了另一種實現,因此要用一個新的標誌
-XX:+CMSConcurrentMTEnabled
CMS的各個階段使用多執行緒並行處理
-XX:ConcGCThreads
併發處理執行緒數,設定為4時表示CMS的每個階段都使用4個執行緒.預設為** (ParallelGCThreads + 3)/4**
-XX:CMSInitiatingOccupancyFraction
GC閾值,當記憶體佔用超過該點時觸發gc.對於CMS不能等到記憶體滿了再去GC,因為GC期間業務執行緒也是在執行的,會進行記憶體分配.
-XX+UseCMSInitiatingOccupancyOnly
強制使用-XX:CMSInitiatingOccupancyFraction指定的GC閾值,不再由JVM智慧推斷.
-XX:+CMSClassUnloadingEnabled
開啟永久代收集.預設只會在永久代滿時才會使用清理,並且不是並行的.
-XX:+CMSIncrementalMode
GC是定期停止gc執行緒,對於現在的機器意義不大.
System.gc()的優化
-XX:+ExplicitGCInvokesConcurrent
呼叫System.gc()後使用CMS的GC而非full gc.
XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
與上面類似,額外增加對永久代的gc.
關閉System.gc()
-XX:+DisableExplicitGC
作者建議這個標誌值得新增.
八 GC日誌
gc日誌並不複雜,簡要說明一下
-XX:+PrintGC
列印簡要GC日誌
//[GC型別,GC前記憶體佔用->GC後佔用(堆記憶體),GC消耗的真實時間] [GC 246656K->243120K(376320K), 0,0929090 secs] [Full GC 243120K->241951K(629760K), 1,5589690 secs]
-XX:+PrintGCDetails
列印詳細日誌
[GC [PSYoungGen: 142816K->10752K(142848K)] 246648K->243136K(375296K), 0,0935090 secs ] //user表示所有GC執行緒消耗的總時間, sys為系統態時間,應該是GC時上下文切換等的額外消耗,real為消耗的真實時間 // [Times: user=0,55 sys=0,10, real=0,09 secs]
更詳細的日誌
[Full GC [PSYoungGen: 10752K->9707K(142848K)] //表示堆記憶體變化 [ParOldGen: 232384K->232244K(485888K)] 243136K->241951K(628736K) //永久代是不算在堆記憶體的 [PSPermGen: 3162K->3161K(21504K)], 1,5265450 secs ] [Times: user=10,96 sys=0,06, real=1,53 secs]
根據每代gc前後的佔用記憶體變化可以推斷到底是哪一代引發的gc,像上面的例子,三代基本都沒變化,那麼有可能是ergonomics 引起的(即使佔用未滿,ergonomics也可能主動引發一次gc).對於System.gc()引發的GC,GCtype會是 Full GC (System)
新增時間資訊
-XX:+PrintGCTimeStamps and -XX:+PrintGCDateStamps
如下
//-XX:+PrintGCTimeStamps 0,185: [GC 66048K->53077K(251392K), 0,0977580 secs] //-XX:+PrintGCDateStamps 2014-01-03T12:08:38.102-0100: [GC 66048K->53077K(251392K), 0,0959470 secs]
把GC日誌記錄到檔案中
-Xloggc
-Xloggc:<file>,把gc日誌記錄到制定檔案中,-XX:+PrintGC and -XX:+PrintGCTimeStamps會被隱式使用
結語
雖然沒有看過JVM的原始碼,單從這些設定就能看出實現是多麼複雜.
未來的路還很長.