JVM调优:运行参数,内存模型,mat、jps、jstat、jmap、jstack、jvisualvm工具的使用
JVM調(diào)優(yōu) - 工具篇
作者:張學(xué)亮
講解內(nèi)容
-
了解下我們?yōu)槭裁匆獙W(xué)習(xí)JVM優(yōu)化
-
掌握jvm的運(yùn)行參數(shù)以及參數(shù)的設(shè)置
-
掌握jvm的內(nèi)存模型(堆內(nèi)存)
-
掌握jamp命令的使用以及通過MAT工具進(jìn)行分析
-
掌握定位分析內(nèi)存溢出的方法
-
掌握jstack命令的使用
-
掌握VisualJVM工具的使用
1、為什么要對jvm進(jìn)行優(yōu)化?
在本地開發(fā)環(huán)境中我們很少會(huì)遇到需要對jvm進(jìn)行優(yōu)化的需求,但是到了生產(chǎn)環(huán)境,我們
可能將有下面的需求:
-
運(yùn)行的應(yīng)用“卡住了”,日志不輸出,程序沒有反應(yīng)
-
服務(wù)器的CPU負(fù)載突然升高
-
在多線程應(yīng)用下,如何分配線程的數(shù)量?
-
……
在本次課程中,我們將對jvm有更深入的學(xué)習(xí),我們不僅要讓程序能跑起來,而且是可以
跑的更快!可以分析解決在生產(chǎn)環(huán)境中所遇到的各種“棘手”的問題。
說明:本套課程使用的jdk版本為1.8.0_201。
PS:超過該版本oracle版jdk將收費(fèi)。
2、jvm的運(yùn)行參數(shù)
在jvm中有很多的參數(shù)可以進(jìn)行設(shè)置,這樣可以讓jvm在各種環(huán)境中都能夠高效的運(yùn)行。 絕大部分的參數(shù)保持默認(rèn)即可。
2.1、三種參數(shù)類型
jvm的參數(shù)類型分為三類,分別是:
- 標(biāo)準(zhǔn)參數(shù)
- -help
- -version
- -X參數(shù) (非標(biāo)準(zhǔn)參數(shù))
- -Xint
- -Xcomp
- -XX參數(shù)(使用率較高)
- -XX:newSize
- -XX:+UseSerialGC
2.2、標(biāo)準(zhǔn)參數(shù)
jvm的標(biāo)準(zhǔn)參數(shù),一般都是很穩(wěn)定的,在未來的JVM版本中不會(huì)改變,可以使用java -help 檢索出所有的標(biāo)準(zhǔn)參數(shù)。
有關(guān)詳細(xì)信息, 請參閱
2.2.1、實(shí)戰(zhàn)
實(shí)戰(zhàn)1:查看jvm版本
$ java -version java version "1.8.0_201" Java(TM) SE Runtime Environment (build 1.8.0_201-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)# `‐showversion`參數(shù)是表示,先打印版本信息,再執(zhí)行后面的命令,在調(diào)試時(shí)非常有用, 后面會(huì)使用到。實(shí)戰(zhàn)2:通過-D設(shè)置系統(tǒng)屬性參數(shù)
package cn.zxl.jvm;public class TestJVM {public static void main(String[] args) {String str = System.getProperty("str");if(str == null){System.out.println("dmgeo");}else{System.out.println(str);}System.gc();} }進(jìn)行編譯、測試:
打印結(jié)果:
上述配置就相當(dāng)于命令行如下指令:
2.2.2、-server與-client參數(shù)
可以通過-server或-client設(shè)置jvm的運(yùn)行參數(shù)。
- 它們的區(qū)別是Server VM的初始堆空間會(huì)大一些,默認(rèn)使用的是并行垃圾回收器,啟 動(dòng)慢運(yùn)行快。
- Client VM相對來講會(huì)保守一些,初始堆空間會(huì)小一些,使用串行的垃圾回收器,它 的目標(biāo)是為了讓JVM的啟動(dòng)速度更快,但運(yùn)行速度會(huì)比Serverm模式慢些。
- JVM在啟動(dòng)的時(shí)候會(huì)根據(jù)硬件和操作系統(tǒng)自動(dòng)選擇使用Server還是Client類型的 JVM。 32位操作系統(tǒng) 如果是Windows系統(tǒng),不論硬件配置如何,都默認(rèn)使用Client類型的JVM。 如果是其他操作系統(tǒng)上,機(jī)器配置有2GB以上的內(nèi)存同時(shí)有2個(gè)以上CPU的話默 認(rèn)使用server模式,否則使用client模式。 64位操作系統(tǒng) 只有server類型,不支持client類型。
測試:
由于本機(jī)是64位系統(tǒng),所以不支持client模式。
2.3、-X參數(shù)
jvm的-X參數(shù)是非標(biāo)準(zhǔn)參數(shù),在不同版本的jvm中,參數(shù)可能會(huì)有所不同,可以通過java - X查看非標(biāo)準(zhǔn)參數(shù)。
D:\develop\workspace\java\zxl-jvm\zxl-jvm-test\src\main\java $ java -X-Xmixed 混合模式執(zhí)行 (默認(rèn))-Xint 僅解釋模式執(zhí)行-Xbootclasspath:<用 ; 分隔的目錄和 zip/jar 文件>設(shè)置搜索路徑以引導(dǎo)類和資源-Xbootclasspath/a:<用 ; 分隔的目錄和 zip/jar 文件>附加在引導(dǎo)類路徑末尾-Xbootclasspath/p:<用 ; 分隔的目錄和 zip/jar 文件>置于引導(dǎo)類路徑之前-Xdiag 顯示附加診斷消息-Xnoclassgc 禁用類垃圾收集-Xincgc 啟用增量垃圾收集-Xloggc:<file> 將 GC 狀態(tài)記錄在文件中 (帶時(shí)間戳)-Xbatch 禁用后臺(tái)編譯-Xms<size> 設(shè)置初始 Java 堆大小-Xmx<size> 設(shè)置最大 Java 堆大小-Xss<size> 設(shè)置 Java 線程堆棧大小-Xprof 輸出 cpu 配置文件數(shù)據(jù)-Xfuture 啟用最嚴(yán)格的檢查, 預(yù)期將來的默認(rèn)值-Xrs 減少 Java/VM 對操作系統(tǒng)信號的使用 (請參閱文檔)-Xcheck:jni 對 JNI 函數(shù)執(zhí)行其他檢查-Xshare:off 不嘗試使用共享類數(shù)據(jù)-Xshare:auto 在可能的情況下使用共享類數(shù)據(jù) (默認(rèn))-Xshare:on 要求使用共享類數(shù)據(jù), 否則將失敗。-XshowSettings 顯示所有設(shè)置并繼續(xù)-XshowSettings:all顯示所有設(shè)置并繼續(xù)-XshowSettings:vm 顯示所有與 vm 相關(guān)的設(shè)置并繼續(xù)-XshowSettings:properties顯示所有屬性設(shè)置并繼續(xù)-XshowSettings:locale顯示所有與區(qū)域設(shè)置相關(guān)的設(shè)置并繼續(xù)-X 選項(xiàng)是非標(biāo)準(zhǔn)選項(xiàng), 如有更改, 恕不另行通知。2.3.1、-Xint、-Xcomp、-Xmixed
- 在解釋模式(interpreted mode)下,-Xint標(biāo)記會(huì)強(qiáng)制JVM執(zhí)行所有的字節(jié)碼,當(dāng)然這 會(huì)降低運(yùn)行速度,通常低10倍或更多。
- -Xcomp參數(shù)與它(-Xint)正好相反,JVM在第一次使用時(shí)會(huì)把所有的字節(jié)碼編譯成 本地代碼,從而帶來最大程度的優(yōu)化。
- 然而,很多應(yīng)用在使用-Xcomp也會(huì)有一些性能損失,當(dāng)然這比使用-Xint損失的 少,原因是-xcomp沒有讓JVM啟用JIT編譯器的全部功能。JIT編譯器可以對是否 需要編譯做判斷,如果所有代碼都進(jìn)行編譯的話,對于一些只執(zhí)行一次的代碼就 沒有意義了。
- -Xmixed是混合模式,將解釋模式與編譯模式進(jìn)行混合使用,由jvm自己決定,這是 jvm默認(rèn)的模式,也是推薦使用的模式。
示例:強(qiáng)制設(shè)置運(yùn)行模式
注意:編譯模式下,第一次執(zhí)行會(huì)比解釋模式下執(zhí)行慢一些,注意觀察。
2.4、-XX參數(shù)
XX參數(shù)也是非標(biāo)準(zhǔn)參數(shù),主要用于jvm的調(diào)優(yōu)和debug操作。
-XX參數(shù)的使用有2種方式,一種是boolean類型,一種是非boolean類型:
- boolean類型
- 格式:-XX:[+-]
- 如:-XX:+DisableExplicitGC 表示禁用手動(dòng)調(diào)用gc操作,也就是說調(diào)用 System.gc()無效
- 非boolean類型
- 格式:-XX:
- 如:-XX:NewRatio=1 表示新生代和老年代的比值
用法:
2.5、-Xms與-Xmx參數(shù)
-Xms與-Xmx分別是設(shè)置jvm的堆內(nèi)存的初始大小和最大大小。
-Xmx2048m:等價(jià)于-XX:MaxHeapSize,設(shè)置JVM最大堆內(nèi)存為2048M。
-Xms512m:等價(jià)于-XX:InitialHeapSize,設(shè)置JVM初始堆內(nèi)存為512M。
適當(dāng)?shù)恼{(diào)整jvm的內(nèi)存大小,可以充分利用服務(wù)器資源,讓程序跑的更快。
示例:
2.6、查看jvm的運(yùn)行參數(shù)
有些時(shí)候我們需要查看jvm的運(yùn)行參數(shù),這個(gè)需求可能會(huì)存在2種情況:
- 第一,運(yùn)行java命令時(shí)打印出運(yùn)行參數(shù);
- 第二,查看正在運(yùn)行的java進(jìn)程的參數(shù);
2.6.1、運(yùn)行java命令時(shí)打印參數(shù)
運(yùn)行java命令時(shí)打印參數(shù),需要添加-XX:+PrintFlagsFinal參數(shù)即可。
java -XX:+PrintFlagsFinal -version
由上述的信息可以看出,參數(shù)有boolean類型和數(shù)字類型,值的操作符是=或:=,分別代表默認(rèn)值和被修改的值。
修改參數(shù)VerifySharedSpaces的值
java -XX:+PrintFlagsFinal -XX:+VerifySharedSpaces -version
可以看到VerifySharedSpaces這個(gè)參數(shù)已經(jīng)被修改了。
- 開啟:
java -XX:+PrintFlagsFinal -XX:+VerifySharedSpaces -version|findstr VerifySharedSpaces - 關(guān)閉:
java -XX:+PrintFlagsFinal -XX:-VerifySharedSpaces -version|findstr VerifySharedSpaces
2.6.2、查看正在運(yùn)行的jvm參數(shù)
如果想要查看正在運(yùn)行的jvm就需要借助于jinfo命令查看。
首先,啟動(dòng)一個(gè)tomcat用于測試,來觀察下運(yùn)行的jvm參數(shù)。
訪問:localhoost:8080
- 通過jps或者 jps ‐l查看java進(jìn)程
- 查看所有的參數(shù),用法:jinfo ‐flags <進(jìn)程id>
- 查看某一參數(shù)的值,用法:jinfo ‐flag <參數(shù)名> <進(jìn)程id>
3、jvm的內(nèi)存模型
jvm的內(nèi)存模型在1.7和1.8有較大的區(qū)別,雖然本套課程是以1.8為例進(jìn)行講解,但是我們 也是需要對1.7的內(nèi)存模型有所了解,所以接下里,我們將先學(xué)習(xí)1.7再學(xué)習(xí)1.8的內(nèi)存模型。
3.1、jdk1.7的堆內(nèi)存模型
-
Young 年輕區(qū)(代)
Young區(qū)被劃分為三部分,Eden區(qū)和兩個(gè)大小嚴(yán)格相同的Survivor區(qū),其中, Survivor區(qū)間中,某一時(shí)刻只有其中一個(gè)是被使用的,另外一個(gè)留做垃圾收集時(shí)復(fù)制 對象用,在Eden區(qū)間變滿的時(shí)候, GC就會(huì)將存活的對象移到空閑的Survivor區(qū)間 中,根據(jù)JVM的策略,在經(jīng)過幾次垃圾收集后,仍然存活于Survivor的對象將被移動(dòng) 到Tenured區(qū)間。 -
Tenured 年老區(qū)
Tenured區(qū)主要保存生命周期長的對象,一般是一些老的對象,當(dāng)一些對象在Young 復(fù)制轉(zhuǎn)移一定的次數(shù)以后,對象就會(huì)被轉(zhuǎn)移到Tenured區(qū),一般如果系統(tǒng)中用了 application級別的緩存,緩存中的對象往往會(huì)被轉(zhuǎn)移到這一區(qū)間。 -
Perm 永久區(qū)
Perm代主要保存class,method,filed對象,這部份的空間一般不會(huì)溢出,除非一次性加載了很多的類,不過在涉及到熱部署的應(yīng)用服務(wù)器的時(shí)候,有時(shí)候會(huì)遇到 java.lang.OutOfMemoryError : PermGen space 的錯(cuò)誤,造成這個(gè)錯(cuò)誤的很大原因 就有可能是每次都重新部署,但是重新部署后,類的class沒有被卸載掉,這樣就造 成了大量的class對象保存在了perm中,這種情況下,一般重新啟動(dòng)應(yīng)用服務(wù)器可以解決問題。 -
Virtual區(qū)
最大內(nèi)存和初始內(nèi)存的差值,就是Virtual區(qū)。
3.2、jdk1.8的堆內(nèi)存模型
由上圖可以看出,jdk1.8的內(nèi)存模型是由2部分組成,年輕代 + 年老代。
年輕代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中變化最大的Perm區(qū),用Metaspace(元數(shù)據(jù)空間)進(jìn)行了替換。
需要特別說明的是:Metaspace所占用的內(nèi)存空間不是在虛擬機(jī)內(nèi)部,而是在本地內(nèi)存空間中,這也是與1.7的永久代最大的區(qū)別所在。
3.3、為什么要廢棄1.7中的永久區(qū)?
官網(wǎng)給出了解釋:http://openjdk.java.net/jeps/122
This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.
移除永久代是為融合HotSpot JVM與 JRockit VM而做出的努力,因?yàn)镴Rockit沒有永久代,不需要配置永久代。
現(xiàn)實(shí)使用中,由于永久代內(nèi)存經(jīng)常不夠用或發(fā)生內(nèi)存泄露,爆出異常java.lang.OutOfMemoryError: PermGen。
基于此,將永久區(qū)廢棄,而改用元空間,改為了使用本地內(nèi)存空間。
3.4、通過jstat命令進(jìn)行查看堆內(nèi)存使用情況
jstat命令可以查看堆內(nèi)存各部分的使用量,以及加載類的數(shù)量。命令的格式如下:
jstat [-命令選項(xiàng)] [vmid] [間隔時(shí)間/毫秒] [查詢次數(shù)]
3.4.1、查看class加載統(tǒng)計(jì)
$ jps D:\develop\apache-tomcat-7.0.76\bin $ jstat -class 8212 Loaded Bytes Unloaded Bytes Time11844 21107.6 0 0.0 7.11說明:
- Loaded:加載class的數(shù)量
- Bytes:所占用空間大小
- Unloaded:未加載數(shù)量
- Bytes:未加載占用空間
- Time:時(shí)間
3.4.2、查看編譯統(tǒng)計(jì)
D:\develop\apache-tomcat-7.0.76\bin $ jstat -compiler 8212 Compiled Failed Invalid Time FailedType FailedMethod5043 1 0 28.86 1 org/apache/tomcat/util/IntrospectionUtils setProperty
說明:
- Compiled:編譯數(shù)量。
- Failed:失敗數(shù)量
- Invalid:不可用數(shù)量
- Time:時(shí)間
- FailedType:失敗類型
- FailedMethod:失敗的方法
3.4.3、垃圾回收統(tǒng)計(jì)
D:\develop\apache-tomcat-7.0.76\bin $ jstat -gc 8212S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 36352.0 38912.0 0.0 0.0 449536.0 83013.6 169984.0 45310.6 59028.0 58273.7 8192.0 8036.3 10 0.446 3 0.442 0.888
說明:
- S0C:第一個(gè)Survivor區(qū)的大小(KB)
- S1C:第二個(gè)Survivor區(qū)的大小(KB)
- S0U:第一個(gè)Survivor區(qū)的使用大小(KB)
- S1U:第二個(gè)Survivor區(qū)的使用大小(KB)
- EC:Eden區(qū)的大小(KB)
- EU:Eden區(qū)的使用大小(KB)
- OC:Old區(qū)大小(KB)
- OU:Old使用大小(KB)
- MC:方法區(qū)大小(KB)
- MU:方法區(qū)使用大小(KB)
- CCSC:壓縮類空間大小(KB)
- CCSU:壓縮類空間使用大小(KB)
- YGC:年輕代垃圾回收次
- FGC:老年代垃圾回收次數(shù)
- FGCT:老年代垃圾回收消耗時(shí)間
- GCT:垃圾回收消耗總時(shí)間
4、jmap的使用以及內(nèi)存溢出分析
前面通過jstat可以對jvm堆的內(nèi)存進(jìn)行統(tǒng)計(jì)分析,而jmap可以獲取到更加詳細(xì)的內(nèi)容,
如:內(nèi)存使用情況的匯總、對內(nèi)存溢出的定位與分析。
4.1、查看內(nèi)存使用情況
D:\develop\apache-tomcat-7.0.76\bin $ jmap -heap 8212 Attaching to process ID 8212, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.101-b13using thread-local object allocation. Parallel GC with 4 thread(s)Heap Configuration: #堆內(nèi)存配置信息MinHeapFreeRatio = 0MaxHeapFreeRatio = 100MaxHeapSize = 3143630848 (2998.0MB)NewSize = 65536000 (62.5MB)MaxNewSize = 1047527424 (999.0MB)OldSize = 131596288 (125.5MB)NewRatio = 2SurvivorRatio = 8MetaspaceSize = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize = 17592186044415 MBG1HeapRegionSize = 0 (0.0MB)Heap Usage: PS Young Generation Eden Space:capacity = 460324864 (439.0MB)used = 85005936 (81.06797790527344MB)free = 375318928 (357.93202209472656MB)18.466509773410806% used From Space:capacity = 37224448 (35.5MB)used = 0 (0.0MB)free = 37224448 (35.5MB)0.0% used To Space:capacity = 39845888 (38.0MB)used = 0 (0.0MB)free = 39845888 (38.0MB)0.0% used PS Old Generation #老年代capacity = 174063616 (166.0MB)used = 46398096 (44.24867248535156MB)free = 127665520 (121.75132751464844MB)26.655826798404554% used19142 interned Strings occupying 1739112 bytes.4.2、查看內(nèi)存中對象數(shù)量及大小
-
查看所有對象,包括活躍以及非活躍的
jmap ‐histo <pid> | more -
查看活躍對象
jmap ‐histo:live <pid> | more
4.3、將內(nèi)存使用情況dump到文件中
有些時(shí)候我們需要將jvm當(dāng)前內(nèi)存中的情況dump到文件中,然后對它進(jìn)行分析,jmap也 是支持dump到文件中的。
#用法: jmap ‐dump:format=b,file=dumpFileName <pid>#示例 jmap ‐dump:format=b,file=dump.dat 8212可以看到已經(jīng)在D:\develop\workspace\java\zxl-jvm\zxl-jvm-test\data下生成了dump.dat的文件。
D:\develop\workspace\java\zxl-jvm\zxl-jvm-test\data $ jmap -dump:format=b,file=dump.dat 8212 Dumping heap to D:\develop\workspace\java\zxl-jvm\zxl-jvm-test\data\dump.dat ... Heap dump file createdD:\develop\workspace\java\zxl-jvm\zxl-jvm-test\data $ dir驅(qū)動(dòng)器 D 中的卷是 軟件卷的序列號是 960C-77CED:\develop\workspace\java\zxl-jvm\zxl-jvm-test\data 的目錄2020/10/11 17:50 <DIR> . 2020/10/11 17:50 <DIR> .. 2020/10/11 17:50 78,462,398 dump.dat1 個(gè)文件 78,462,398 字節(jié)2 個(gè)目錄 593,156,988,928 可用字節(jié)4.4、通過jhat對dump文件進(jìn)行分析
在上一小節(jié)中,我們將jvm的內(nèi)存dump到文件中,這個(gè)文件是一個(gè)二進(jìn)制的文件,不方便查看,這時(shí)我們可以借助于jhat工具進(jìn)行查看。
#用法: jhat ‐port <port> <file>示例:
D:\develop\workspace\java\zxl-jvm\zxl-jvm-test\data $ jhat -port 9999 dump.dat Reading from dump.dat... Dump file created Sun Oct 11 17:50:10 CST 2020 Snapshot read, resolving... Resolving 617627 objects... Chasing references, expect 123 dots........................................................................................................................... Eliminating duplicate references........................................................................................................................... Snapshot resolved. Started HTTP server on port 9999 Server is ready.打開瀏覽器進(jìn)行訪問:http://localhost:9999/
在最后面有OQL查詢功能。
4.5、通過MAT工具對dump文件進(jìn)行分析
4.5.1、MAT工具介紹
MAT(Memory Analyzer Tool),一個(gè)基于Eclipse的內(nèi)存分析工具,是一個(gè)快速、功能豐 富的JAVA heap分析工具,它可以幫助我們查找內(nèi)存泄漏和減少內(nèi)存消耗。使用內(nèi)存分析工具從眾多的對象中進(jìn)行分析,快速的計(jì)算出在內(nèi)存中對象的占用大小,看看是誰阻止 了垃圾收集器的回收工作,并可以通過報(bào)表直觀的查看到可能造成這種結(jié)果的對象。
官網(wǎng)地址:https://www.eclipse.org/mat/
4.5.2、下載安裝
下載地址:https://www.eclipse.org/mat/downloads.php
將下載得到的MemoryAnalyzer-1.10.0.20200225-win32.win32.x86_64.zip進(jìn)行解壓:
4.5.3、使用
第一步:open
第二步:選擇要分析的堆轉(zhuǎn)儲(chǔ)文件
第三步:
第四步:點(diǎn)擊【Overview】
第五步:點(diǎn)擊【Dominator Tree】搜索對象
查看對象以及它的依賴:
查看可能存在內(nèi)存泄露的分析:
總結(jié)
以上是生活随笔為你收集整理的JVM调优:运行参数,内存模型,mat、jps、jstat、jmap、jstack、jvisualvm工具的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux kill命令使用
- 下一篇: golang基本数据类型和string的