有的同窗雖然寫了一段時間 Java 了,可是對於 JVM 卻不太關注。有的同窗說,參數都是團隊規定好的,部署的時候也不用我動手,關注它有什麼用,並且,JVM 這東西,聽上去就感受很神祕很高深的樣子,仍是算了吧。java
沒錯,部署的時候可能用不到你親自動手,可是出現問題了怎麼辦,難道不用你解決問題嗎,若是對 JVM 瞭解不夠的話,有些問題可能排查起來就很費力,或者根本沒法解決。服務器
本篇以 JDK Hotspot 8 爲背景,介紹一下 JVM 的經常使用參數。建議你在作一些小項目、小 demo 的時候,也把這些參數加上,加深印象。以個人經驗來看,有些知識你剛開始接觸的時候會感受很難理解,可是不要緊,萬事開頭難嘛,知識點都是須要消化時間的。第一天不理解,甚至過了一個月也不理解,可是總有那麼一刻,你會忽然有種茅塞頓開的感受,感受一會兒通了。最後內心面感謝本身在多少多少天之前可以開始學習並堅持學習這些知識點。app
只介紹一些經常使用參數,除了這些經常使用參數外,Hotspot 還提供了不少其餘的參數,每個都值得考究。jvm
在使用這些參數以前,你須要對 Java 內存模型有必定的瞭解,瞭解一下內存模型。ide
仍是要把內存模型圖放在這裏,方便理解。工具
-Xms: 堆的初始值,例如 -Xmx2048,初始堆大小爲 2G性能
-Xmx: 堆的最大值,例如 -Xmx2048M,容許最大堆內存 2G學習
-Xmn: 新生代大小spa
-XX:SurvivorRatio:Eden 區所佔比例,默認是 8,也就是 80%,例如 -XX:SurvivorRatio=8.net
最好將 -Xms 和 -Xmx 的值設置成同樣的值,這樣作是爲了防止隨着堆空間使用量增長,會動態的調整堆空間大小,有必定的性能損耗,不如開始就設置成相同的值,來規避性能損失。
-Xss:棧空間大小,棧是線程獨佔的,因此是一個線程使用棧空間的大小,例如 -Xss256K,若是不設置此參數,默認值是 1M,通常來說設置成 256K 就足夠了。
-XX:MetaspaceSize:Metaspace 空間初始大小,若是不設置的話,默認是20.79M,這個初始大小是觸發首次 Metaspace Full GC 的閾值,例如 -XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize:Metaspace 最大值,默認不限制大小,可是線上環境建議設置,例如
-XX:MaxMetaspaceSize=256M
-XX:MinMetaspaceFreeRatio:最小空閒比,當 Metaspace 發生 GC 後,會計算 Metaspace 的空閒比,若是空閒比(空閒空間/當前 Metaspace 大小)小於此值,就會觸發 Metaspace 擴容。默認值是 40 ,也就是 40%,例如 -XX:MinMetaspaceFreeRatio=40
-XX:MaxMetaspaceFreeRatio:最大空閒比,當 Metaspace 發生 GC 後,會計算 Metaspace 的空閒比,若是空閒比(空閒空間/當前 Metaspace 大小)大於此值,就會觸發 Metaspace 釋放空間。默認值是 70 ,也就是 70%,例如 -XX:MaxMetaspaceFreeRatio=70
建議將 MetaspaceSize 和 MaxMetaspaceSize 設置爲一樣大小,避免頻繁擴容。
簡單日誌
-verbose:gc 或者 -XX:+PrintGC
日誌格式:
[GC (Allocation Failure) 7892K->5646K(19456K), 0.0060442 secs] [GC (Allocation Failure) , 0.0066315 secs] [Full GC (Allocation Failure) 19302K->13646K(19456K), 0.0032698 secs]
詳細日誌
#打印詳細日誌 -XX:+PrintGCDetails #打印 GC 的時間點 -XX:+PrintGCDateStamps
日誌格式:
2019-11-13T14:06:46.099-0800: [GC (Allocation Failure) 2019-11-13T14:06:46.099-0800: [DefNew (promotion failed) : 9180K->9157K(9216K), 0.0084297 secs]2019-11-13T14:06:46.107-0800: [Tenured: 10145K->10145K(10240K), 0.0035768 secs] 13802K->13646K(19456K), [Metaspace: 3895K->3895K(1056768K)], 0.0120887 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 2019-11-13T14:06:47.243-0800: [Full GC (Allocation Failure) 2019-11-13T14:06:47.244-0800: [Tenured: 10145K->10145K(10240K), 0.0042686 secs] 19304K->19146K(19456K), [Metaspace: 3895K->3895K(1056768K)], 0.0043232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
如下幾個 GC 日誌相關的參數打印的內容比較多,生產環境可選擇性開啓,大多數時候不須要開啓。
GC 先後的堆信息
-XX:+PrintHeapAtGC
{Heap before GC invocations=0 (full 0): def new generation total 9216K, used 7892K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 96% used [0x00000007bec00000, 0x00000007bf3b5200, xxx.... class space used 445K, capacity 462K, committed 512K, reserved 1048576K Heap after GC invocations=1 (full 0): def new generation total 9216K, used 1023K [0x00000007bec00000, xxx... Metaspace used 3892K, capacity 4646K, committed 4864K, reserved 1056768K class space used 445K, capacity 462K, committed 512K, reserved 1048576K }
GC 致使的 Stop the world 時間
-XX:+PrintGCApplicationStoppedTime
Total time for which application threads were stopped: 0.0070384 seconds, Stopping threads took: 0.0000200 seconds
加載類信息
-verbose:class
[Loaded java.net.URLClassLoader$3$1 from /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/jre/lib/rt.jar]
GC 先後的類加載狀況
-XX:+PrintClassHistogramBeforeFullGC -XX:+PrintClassHistogramAfterFullGC
num #instances #bytes class name ---------------------------------------------- 1: 140 19016264 [B 2: 2853 226256 [C 3: 138 169072 [I 4: 761 86240 java.lang.Class 5: 2850 68400 java.lang.String 6: 660 41024 [Ljava.lang.Object;
日誌輸出到文件
以上參數配置好以後,默認會輸出到控制檯或者服務指定的統一日誌的位置。可是這裏還會有服務的通常性信息日誌、錯誤日誌等,都混在一塊兒的話會比較亂,因此,通常都會把 jvm 日誌單獨存放。
#GC 活動日誌,根據配置的參數輸出內容 -Xloggc:/Users/fengzheng/jvmlog/gc.log #致命錯誤日誌,只有在 jvm 發生崩潰的時候會輸出 -XX:ErrorFile=/Users/fengzheng/jvmlog/hs_err_pid%p.log
堆溢出現場保留
有些錯誤雖然不會致使 jvm 崩潰,可是對於服務而言也是很是嚴重的,好比stackOverflow、OutOfMemoryError,發生錯誤後,留存現場信息對分析錯誤緣由是相當重要的。jvm 提供了保留堆溢出現場的方法,對於 JDK 8 而言,多是 heap 溢出,也多是 Metasapce 溢出。
-XX:HeapDumpPath=/Users/fengzheng/jvmlog -XX:+HeapDumpOnOutOfMemoryError
最後出現異常後,保存的文件格式爲 java_pidxxx.hprof,pid 後面是發生溢出的進程 id,以後能夠用 VisualVM、JProfiler 等工具打開分析。
隨着 JDK 版本的升級,可以使用的垃圾收集器類型也愈來愈多了。JDK 8 可以使用的垃圾收集器有 7 種,固然有點只適用於年輕代,有點只使用於老年代,JDK 8 中最新的垃圾收集器是 G1,能夠用於年輕代和老年代。到了 JDK 11,還出了 ZGC。
下圖是 JDK 8 中可以使用的垃圾收集器以及它們配合使用的關係。
Serial、ParNew、Parallel Scavenge 只適用於年輕代,CMS、Serial Old、Parallel Old 只適用於老年代,而 G1 通用於年輕代和老年代。連線表示它們之間可配合使用的關係,其中 CMS 和 Serial Old 連線的意思是說 Serial Old 會做爲 CMS 的後預案,當 CMS 發生 Concurrent Mode Failure 時啓用。
在 JDK 8 中,若是不指定垃圾收集器,默認使用參數 -XX:+UseParallelGC
,新生代使用 Parallel Scavenge,老年代使用 Serial Old。
-XX:+UseSerialGC:使用 Serial + Serial Old ,運行於 client 模式下的默認設置
-XX:+UseConcMarkSweepGC:使用 ParNew+CMS+Serial Old,CMS 垃圾收集器
-XX:+UseParallelGC:Parallel Scavenge + Serial Old,JDK 8 server 模式下的默認設置
-XX:+UseParallelOldGC:Parallel Scavenge + Parallel Old
-XX:+UseG1GC:使用 G1 垃圾收集器
除了日誌外,當咱們須要實時查看 JVM 運行狀況的時候怎麼辦,固然能夠到 JVM 所在服務器用 jstack、jmap、jinfo 等工具進行查看,可是又不夠直觀,這時候就須要開啓 JMX 遠程功能,使用 jConsole、VisualVM 等工具進行監控。或者本身開發監控平臺
開啓參數以下:
-Dcom.sun.management.jmxremote #指定 jvm 所在服務器 ip 或域名 -Djava.rmi.server.hostname=192.168.1.1 #指定端口 -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.rmi.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false