1、我們為什么要對jvm做優化?
在本地開發環境中我們很少會遇到需要對jvm進行優化的需求,但是到了生產環境,我們可能將有下面的需求:
- 運行的應用“卡住了”,日志不輸出,程序沒有反應
- 服務器的CPU負載突然升高
- 在多線程應用下,如何分配線程的數量?
- ……
我們不僅要讓程序能跑起來,而且是可以跑的更快!可以分析解決在生產環境中所遇到的各種“棘手”的問題。
說明:使用的jdk版本為1.8。
2、jvm的運行參數
在jvm中有很多的參數可以進行設置,這樣可以讓jvm在各種環境中都能夠高效的運行。絕大部分的參數保持默認即可。
2.1、三種參數類型
jvm的參數類型分為三類,分別是:
- 標準參數
- -help
- -version
- -X參數 (非標準參數)
- -Xint
- -Xcomp
- -XX參數(使用率較高)
- -XX:newSize
- -XX:+UseSerialGC
2.2、標準參數
jvm的標準參數,一般都是很穩定的,在未來的JVM版本中不會改變,可以使用java -help檢索出所有的標準參數。
[root@node01 ~]# java -help
用法: java [-options] class [args...]
(執行類)
或 java [-options] -jar jarfile [args...]
(執行 jar 文件)
其中選項包括:
-d32 使用 32 位數據模型 (如果可用)
-d64 使用 64 位數據模型 (如果可用)
-server 選擇 "server" VM
默認 VM 是 server,
因為您是在服務器類計算機上運行。
-cp <目錄和 zip/jar 文件的類搜索路徑>
-classpath <目錄和 zip/jar 文件的類搜索路徑>
用 : 分隔的目錄, JAR 檔案
和 ZIP 檔案列表, 用于搜索類文件。
-D<名稱>=<值>
設置系統屬性
-verbose:[class|gc|jni]
啟用詳細輸出
-version 輸出產品版本并退出
-version:<值>
警告: 此功能已過時, 將在
未來發行版中刪除。
需要指定的版本才能運行
-showversion 輸出產品版本并繼續
-jre-restrict-search | -no-jre-restrict-search
警告: 此功能已過時, 將在
未來發行版中刪除。
在版本搜索中包括/排除用戶專用 JRE
-? -help 輸出此幫助消息
-X 輸出非標準選項的幫助
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
按指定的粒度啟用斷言
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
禁用具有指定粒度的斷言
-esa | -enablesystemassertions
啟用系統斷言
-dsa | -disablesystemassertions
禁用系統斷言
-agentlib:<libname>[=<選項>]
加載本機代理庫 <libname>, 例如 -agentlib:hprof
另請參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:<pathname>[=<選項>]
按完整路徑名加載本機代理庫
-javaagent:<jarpath>[=<選項>]
加載 Java 編程語言代理, 請參閱 java.lang.instrument
-splash:<imagepath>
使用指定的圖像顯示啟動屏幕
2.2.1、實戰
實戰1:查看jvm版本
[root@node01 ~]# java -version
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)
# -showversion參數是表示,先打印版本信息,再執行后面的命令,在調試時非常有用,后面會使用到。
實戰2:通過-D設置系統屬性參數
public class TestJVM {
public static void main(String[] args) {
String str = System.getProperty("str");
if (str == null) {
System.out.println("hello world");
} else {
System.out.println(str);
}
}
}
進行編譯、測試:
#編譯
[root@node01 test]# javac TestJVM.java
#測試
[root@node01 test]# java TestJVM
hello world
[root@node01 test]# java -Dstr=123 TestJVM
123
2.2.2、-server與-client參數
可以通過-server或-client設置jvm的運行參數。
- 它們的區別是Server VM的初始堆空間會大一些,默認使用的是并行垃圾回收器,啟動慢運行快。
- Client VM相對來講會保守一些,初始堆空間會小一些,使用串行的垃圾回收器,它的目標是為了讓JVM的啟動速度更快,但運行速度會比Serverm模式慢些。
- JVM在啟動的時候會根據硬件和操作系統自動選擇使用Server還是Client類型的JVM。
- 32位操作系統
- 如果是Windows系統,不論硬件配置如何,都默認使用Client類型的JVM。
- 如果是其他操作系統上,機器配置有2GB以上的內存同時有2個以上CPU的話默認使用server模式,否則使用client模式。
- 64位操作系統
- 只有server類型,不支持client類型。
測試:
[root@node01 test]# java -client -showversion TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)
hello world
[root@node01 test]# java -server -showversion TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)
hello world
#由于機器是64位系統,所以不支持client模式
2.3、-X參數
jvm的-X參數是非標準參數,在不同版本的jvm中,參數可能會有所不同,可以通過java -X查看非標準參數。
[root@node01 test]# 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 選項是非標準選項, 如有更改, 恕不另行通知。
2.3.1、-Xint、-Xcomp、-Xmixed
- 在解釋模式(interpreted mode)下,-Xint標記會強制JVM執行所有的字節碼,當然這會降低運行速度,通常低10倍或更多。
- -Xcomp參數與它(-Xint)正好相反,JVM在第一次使用時會把所有的字節碼編譯成本地代碼,從而帶來最大程度的優化。
- 然而,很多應用在使用-Xcomp也會有一些性能損失,當然這比使用-Xint損失的少,原因是-xcomp沒有讓JVM啟用JIT編譯器的全部功能。JIT編譯器可以對是否需要編譯做判斷,如果所有代碼都進行編譯的話,對于一些只執行一次的代碼就沒有意義了。
- -Xmixed是混合模式,將解釋模式與編譯模式進行混合使用,由jvm自己決定,這是jvm默認的模式,也是推薦使用的模式。
示例:強制設置運行模式
#強制設置為解釋模式
[root@node01 test]# java -showversion -Xint TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, interpreted mode)
#強制設置為編譯模式
[root@node01 test]# java -showversion -Xcomp TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, compiled mode)
#注意:編譯模式下,第一次執行會比解釋模式下執行慢一些,注意觀察。
#默認的混合模式
[root@node01 test]# java -showversion TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)
hello world
2.4、-XX參數
-XX參數也是非標準參數,主要用于jvm的調優和debug操作。
-XX參數的使用有2種方式,一種是boolean類型,一種是非boolean類型:
- boolean類型
- 格式:-XX:[+-]<name> 表示啟用或禁用<name>屬性
- 如:-XX:+DisableExplicitGC 表示禁用手動調用gc操作,也就是說調用System.gc()無效
- 非boolean類型
- 格式:-XX:<name>=<value> 表示<name>屬性的值為<value>
- 如:-XX:NewRatio=1 表示新生代和老年代的比值
用法:
[root@node01 test]# java -showversion -XX:+DisableExplicitGC TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)
hello world
2.5、-Xms與-Xmx參數
-Xms與-Xmx分別是設置jvm的堆內存的初始大小和最大大小。
-Xmx2048m:等價于-XX:MaxHeapSize,設置JVM最大堆內存為2048M。
-Xms512m:等價于-XX:InitialHeapSize,設置JVM初始堆內存為512M。
適當的調整jvm的內存大小,可以充分利用服務器資源,讓程序跑的更快。
示例:
[root@node01 test]# java -Xms512m -Xmx2048m TestJVM
hello world
2.6、查看jvm的運行參數
有些時候我們需要查看jvm的運行參數,這個需求可能會存在2種情況:
第一,運行java命令時打印出運行參數;
第二,查看正在運行的java進程的參數;
2.6.1、運行java命令時打印參數
運行java命令時打印參數,需要添加-XX:+PrintFlagsFinal參數即可。
[root@node01 test]# java -XX:+PrintFlagsFinal -version
[Global flags]
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
uintx AdaptiveSizePausePolicy = 0 {product}
uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product}
uintx AdaptiveSizePolicyInitializingSteps = 20 {product}
uintx AdaptiveSizePolicyOutputInterval = 0 {product}
uintx AdaptiveSizePolicyWeight = 10 {product}
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 = 0 {product}
…………………………略…………………………………………
bool UseXmmI2D = false {ARCH product}
bool UseXmmI2F = false {ARCH product}
bool UseXmmLoadAndClearUpper = true {ARCH product}
bool UseXmmRegToRegMoveAll = true {ARCH product}
bool VMThreadHintNoPreempt = false {product}
intx VMThreadPriority = -1 {product}
intx VMThreadStackSize = 1024 {pd product}
intx ValueMapInitialSize = 11 {C1 product}
intx ValueMapMaxLoopSize = 8 {C1 product}
intx ValueSearchLimit = 1000 {C2 product}
bool VerifyMergedCPBytecodes = true {product}
bool VerifySharedSpaces = false {product}
intx WorkAroundNPTLTimedWaitHang = 1 {product}
uintx YoungGenerationSizeIncrement = 20 {product}
uintx YoungGenerationSizeSupplement = 80 {product}
uintx YoungGenerationSizeSupplementDecay = 8 {product}
uintx YoungPLABSize = 4096 {product}
bool ZeroTLAB = false {product}
intx hashCode = 5 {product}
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)
由上述的信息可以看出,參數有boolean類型和數字類型,值的操作符是=或:=,分別代表默認值和被修改的值。
示例:
java -XX:+PrintFlagsFinal -XX:+VerifySharedSpaces -version
intx ValueMapInitialSize = 11 {C1 product}
intx ValueMapMaxLoopSize = 8 {C1 product}
intx ValueSearchLimit = 1000 {C2 product}
bool VerifyMergedCPBytecodes = true {product}
bool VerifySharedSpaces := true {product}
intx WorkAroundNPTLTimedWaitHang = 1 {product}
uintx YoungGenerationSizeIncrement = 20 {product}
uintx YoungGenerationSizeSupplement = 80 {product}
uintx YoungGenerationSizeSupplementDecay = 8 {product}
uintx YoungPLABSize = 4096 {product}
bool ZeroTLAB = false {product}
intx hashCode = 5 {product}
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)
#可以看到VerifySharedSpaces這個參數已經被修改了。
2.6.2、查看正在運行的jvm參數
如果想要查看正在運行的jvm就需要借助于jinfo命令查看。
首先,啟動一個tomcat用于測試,來觀察下運行的jvm參數。
cd /tmp/
rz 上傳
tar -xvf apache-tomcat-7.0.57.tar.gz
cd apache-tomcat-7.0.57
cd bin/
./startup.sh
#http://192.168.40.133:8080/ 進行訪問
訪問成功: #查看所有的參數,用法:jinfo -flags <進程id>
#通過jps 或者 jps -l 查看java進程
[root@node01 bin]# jps
6346 Jps
6219 Bootstrap
[root@node01 bin]# jps -l
6358 sun.tools.jps.Jps
6219 org.apache.catalina.startup.Bootstrap
[root@node01 bin]#
[root@node01 bin]# jinfo -flags 6219
Attaching to process ID 6219, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=31457280 -XX:MaxHeapSize=488636416 -XX:MaxNewSize=162529280 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=10485760 -XX:OldSize=20971520 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -Djava.util.logging.config.file=/tmp/apache-tomcat-7.0.57/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/tmp/apache-tomcat-7.0.57/endorsed -Dcatalina.base=/tmp/apache-tomcat-7.0.57 -Dcatalina.home=/tmp/apache-tomcat-7.0.57 -Djava.io.tmpdir=/tmp/apache-tomcat-7.0.57/temp
#查看某一參數的值,用法:jinfo -flag <參數名> <進程id>
[root@node01 bin]# jinfo -flag MaxHeapSize 6219
-XX:MaxHeapSize=488636416
3、jvm的內存模型
jvm的內存模型在1.7和1.8有較大的區別,雖然本套課程是以1.8為例進行講解,但是我們也是需要對1.7的內存模型有所了解,所以接下里,我們將先學習1.7再學習1.8的內存模型。
3.1、jdk1.7的堆內存模型
-
Young 年輕區(代)
Young區被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區,其中,Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時復制對象用,在Eden區間變滿的時候, GC就會將存活的對象移到空閑的Survivor區間中,根據JVM的策略,在經過幾次垃圾收集后,任然存活于Survivor的對象將被移動到Tenured區間。
-
Tenured 年老區
Tenured區主要保存生命周期長的對象,一般是一些老的對象,當一些對象在Young復制轉移一定的次數以后,對象就會被轉移到Tenured區,一般如果系統中用了application級別的緩存,緩存中的對象往往會被轉移到這一區間。
-
Perm 永久區
Perm代主要保存class,method,filed對象,這部份的空間一般不會溢出,除非一次性加載了很多的類,不過在涉及到熱部署的應用服務器的時候,有時候會遇到java.lang.OutOfMemoryError : PermGen space 的錯誤,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署后,類的class沒有被卸載掉,這樣就造成了大量的class對象保存在了perm中,這種情況下,一般重新啟動應用服務器可以解決問題。
-
Virtual區:
- 最大內存和初始內存的差值,就是Virtual區。
3.2、jdk1.8的堆內存模型
由上圖可以看出,jdk1.8的內存模型是由2部分組成,年輕代 + 年老代。
年輕代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中變化最大的Perm區,用Metaspace(元數據空間)進行了替換。
需要特別說明的是:Metaspace所占用的內存空間不是在虛擬機內部,而是在本地內存空間中,這也是與1.7的永久代最大的區別所在。
3.3、為什么要廢棄1.7中的永久區?
官網給出了解釋: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而做出的努力,因為JRockit沒有永久代,不需要配置永久代。
現實使用中,由于永久代內存經常不夠用或發生內存泄露,爆出異常java.lang.OutOfMemoryError: PermGen。
基于此,將永久區廢棄,而改用元空間,改為了使用本地內存空間。
3.4、通過jstat命令進行查看堆內存使用情況
jstat命令可以查看堆內存各部分的使用量,以及加載類的數量。命令的格式如下:
jstat [-命令選項] [vmid] [間隔時間/毫秒] [查詢次數]
3.4.1、查看class加載統計
[root@node01 ~]# jps
7080 Jps
6219 Bootstrap
[root@node01 ~]# jstat -class 6219
Loaded Bytes Unloaded Bytes Time
3273 7122.3 0 0.0 3.98
說明:
- Loaded:加載class的數量
- Bytes:所占用空間大小
- Unloaded:未加載數量
- Bytes:未加載占用空間
- Time:時間
3.4.2、查看編譯統計
[root@node01 ~]# jstat -compiler 6219
Compiled Failed Invalid Time FailedType FailedMethod
2376 1 0 8.04 1 org/apache/tomcat/util/IntrospectionUtils setProperty
說明:
- Compiled:編譯數量。
- Failed:失敗數量
- Invalid:不可用數量
- Time:時間
- FailedType:失敗類型
- FailedMethod:失敗的方法
3.4.3、垃圾回收統計
[root@node01 ~]# jstat -gc 6219
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
9216.0 8704.0 0.0 6127.3 62976.0 3560.4 33792.0 20434.9 23808.0 23196.1 2560.0 2361.6 7 1.078 1 0.244 1.323
#也可以指定打印的間隔和次數,每1秒中打印一次,共打印5次
[root@node01 ~]# jstat -gc 6219 1000 5
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 2560.0 2361.6 7 1.078 1 0.244 1.323
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 2560.0 2361.6 7 1.078 1 0.244 1.323
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 2560.0 2361.6 7 1.078 1 0.244 1.323
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 2560.0 2361.6 7 1.078 1 0.244 1.323
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 2560.0 2361.6 7 1.078 1 0.244 1.323
說明:
- S0C:第一個Survivor區的大小(KB)
- S1C:第二個Survivor區的大小(KB)
- S0U:第一個Survivor區的使用大小(KB)
- S1U:第二個Survivor區的使用大小(KB)
- EC:Eden區的大小(KB)
- EU:Eden區的使用大小(KB)
- OC:Old區大小(KB)
- OU:Old使用大小(KB)
- MC:方法區大小(KB)
- MU:方法區使用大小(KB)
- CCSC:壓縮類空間大小(KB)
- CCSU:壓縮類空間使用大小(KB)
- YGC:年輕代垃圾回收次數
- YGCT:年輕代垃圾回收消耗時間
- FGC:老年代垃圾回收次數
- FGCT:老年代垃圾回收消耗時間
- GCT:垃圾回收消耗總時間
4、jmap的使用以及內存溢出分析
前面通過jstat可以對jvm堆的內存進行統計分析,而jmap可以獲取到更加詳細的內容,如:內存使用情況的匯總、對內存溢出的定位與分析。
4.1、查看內存使用情況
[root@node01 ~]# jmap -heap 6219
Attaching to process ID 6219, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration: #堆內存配置信息
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 488636416 (466.0MB)
NewSize = 10485760 (10.0MB)
MaxNewSize = 162529280 (155.0MB)
OldSize = 20971520 (20.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage: # 堆內存的使用情況
PS Young Generation #年輕代
Eden Space:
capacity = 123731968 (118.0MB)
used = 1384736 (1.320587158203125MB)
free = 122347232 (116.67941284179688MB)
1.1191416594941737% used
From Space:
capacity = 9437184 (9.0MB)
used = 0 (0.0MB)
free = 9437184 (9.0MB)
0.0% used
To Space:
capacity = 9437184 (9.0MB)
used = 0 (0.0MB)
free = 9437184 (9.0MB)
0.0% used
PS Old Generation #年老代
capacity = 28311552 (27.0MB)
used = 13698672 (13.064071655273438MB)
free = 14612880 (13.935928344726562MB)
48.38545057508681% used
13648 interned Strings occupying 1866368 bytes.
4.2、查看內存中對象數量及大小
#查看所有對象,包括活躍以及非活躍的
jmap -histo <pid> | more
#查看活躍對象
jmap -histo:live <pid> | more
[root@node01 ~]# jmap -histo:live 6219 | more
num #instances #bytes class name
----------------------------------------------
1: 37437 7914608 [C
2: 34916 837984 java.lang.String
3: 884 654848 [B
4: 17188 550016 java.util.HashMap$Node
5: 3674 424968 java.lang.Class
6: 6322 395512 [Ljava.lang.Object;
7: 3738 328944 java.lang.reflect.Method
8: 1028 208048 [Ljava.util.HashMap$Node;
9: 2247 144264 [I
10: 4305 137760 java.util.concurrent.ConcurrentHashMap$Node
11: 1270 109080 [Ljava.lang.String;
12: 64 84128 [Ljava.util.concurrent.ConcurrentHashMap$Node;
13: 1714 82272 java.util.HashMap
14: 3285 70072 [Ljava.lang.Class;
15: 2888 69312 java.util.ArrayList
16: 3983 63728 java.lang.Object
17: 1271 61008 org.apache.tomcat.util.digester.CallMethodRule
18: 1518 60720 java.util.LinkedHashMap$Entry
19: 1671 53472 com.sun.org.apache.xerces.internal.xni.QName
20: 88 50880 [Ljava.util.WeakHashMap$Entry;
21: 618 49440 java.lang.reflect.Constructor
22: 1545 49440 java.util.Hashtable$Entry
23: 1027 41080 java.util.TreeMap$Entry
24: 846 40608 org.apache.tomcat.util.modeler.AttributeInfo
25: 142 38032 [S
26: 946 37840 java.lang.ref.SoftReference
27: 226 36816 [[C
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
#對象說明
B byte
C char
D double
F float
I int
J long
Z boolean
[ 數組,如[I表示int[]
[L+類名 其他對象
4.3、將內存使用情況dump到文件中
有些時候我們需要將jvm當前內存中的情況dump到文件中,然后對它進行分析,jmap也是支持dump到文件中的。
#用法:
jmap -dump:format=b,file=dumpFileName <pid>
#示例
jmap -dump:format=b,file=/tmp/dump.dat 6219
可以看到已經在/tmp下生成了dump.dat的文件。
4.4、通過jhat對dump文件進行分析
在上一小節中,我們將jvm的內存dump到文件中,這個文件是一個二進制的文件,不方便查看,這時我們可以借助于jhat工具進行查看。
#用法:
jhat -port <port> <file>
#示例:
[root@node01 tmp]# jhat -port 9999 /tmp/dump.dat
Reading from /tmp/dump.dat...
Dump file created Mon Sep 10 01:04:21 CST 2018
Snapshot read, resolving...
Resolving 204094 objects...
Chasing references, expect 40 dots........................................
Eliminating duplicate references........................................
Snapshot resolved.
Started HTTP server on port 9999
Server is ready.
打開瀏覽器進行訪問:http://192.168.40.133:9999/
在最后面有OQL查詢功能。
4.5、通過MAT工具對dump文件進行分析
4.5.1、MAT工具介紹
MAT(Memory Analyzer Tool),一個基于Eclipse的內存分析工具,是一個快速、功能豐富的JAVA heap分析工具,它可以幫助我們查找內存泄漏和減少內存消耗。使用內存分析工具從眾多的對象中進行分析,快速的計算出在內存中對象的占用大小,看看是誰阻止了垃圾收集器的回收工作,并可以通過報表直觀的查看到可能造成這種結果的對象。
官網地址:https://www.eclipse.org/mat/
4.5.2、下載安裝
下載地址:https://www.eclipse.org/mat/downloads.php
將下載得到的MemoryAnalyzer-1.8.0.20180604-win32.win32.x86_64.zip進行解壓:
4.5.3、使用
查看對象以及它的依賴:
查看可能存在內存泄露的分析:
5、實戰:內存溢出的定位與分析
內存溢出在實際的生產環境中經常會遇到,比如,不斷的將數據寫入到一個集合中,出現了死循環,讀取超大的文件等等,都可能會造成內存溢出。
如果出現了內存溢出,首先我們需要定位到發生內存溢出的環節,并且進行分析,是正常還是非正常情況,如果是正常的需求,就應該考慮加大內存的設置,如果是非正常需求,那么就要對代碼進行修改,修復這個bug。
首先,我們得先學會如何定位問題,然后再進行分析。如何定位問題呢,我們需要借助于jmap與MAT工具進行定位分析。
接下來,我們模擬內存溢出的場景。
5.1、模擬內存溢出
編寫代碼,向List集合中添加100萬個字符串,每個字符串由1000個UUID組成。如果程序能夠正常執行,最后打印ok。
package cn.test.jvm;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class TestJvmOutOfMemory {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
String str = "";
for (int j = 0; j < 1000; j++) {
str += UUID.randomUUID().toString();
}
list.add(str);
}
System.out.println("ok");
}
}
為了演示效果,我們將設置執行的參數,這里使用的是Idea編輯器。
#參數如下:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
5.2、運行測試
測試結果如下:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid5348.hprof ...
Heap dump file created [8137186 bytes in 0.032 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at cn.test.jvm.TestJvmOutOfMemory.main(TestJvmOutOfMemory.java:14)
Process finished with exit code 1
可以看到,當發生內存溢出時,會dump文件到java_pid5348.hprof。
5.3、導入到MAT工具中進行分析
可以看到,有91.03%的內存由Object[]數組占有,所以比較可疑。
分析:這個可疑是正確的,因為已經有超過90%的內存都被它占有,這是非常有可能出現內存溢出的。
查看詳情:
可以看到集合中存儲了大量的uuid字符串。
6、jstack的使用
有些時候我們需要查看下jvm中的線程執行情況,比如,發現服務器的CPU的負載突然增高了、出現了死鎖、死循環等,我們該如何分析呢?
由于程序是正常運行的,沒有任何的輸出,從日志方面也看不出什么問題,所以就需要看下jvm的內部線程的執行情況,然后再進行分析查找出原因。
這個時候,就需要借助于jstack命令了,jstack的作用是將正在運行的jvm的線程情況進行快照,并且打印出來:
#用法:jstack <pid>
[root@node01 bin]# jstack 2203
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.141-b15 mixed mode):
"Attach Listener" #24 daemon prio=9 os_prio=0 tid=0x00007fabb4001000 nid=0x906 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"http-bio-8080-exec-5" #23 daemon prio=5 os_prio=0 tid=0x00007fabb057c000 nid=0x8e1 waiting on condition [0x00007fabd05b8000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
"http-bio-8080-exec-4" #22 daemon prio=5 os_prio=0 tid=0x00007fab9c113800 nid=0x8e0 waiting on condition [0x00007fabd06b9000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
"http-bio-8080-exec-3" #21 daemon prio=5 os_prio=0 tid=0x0000000001aeb800 nid=0x8df waiting on condition [0x00007fabd09ba000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
"http-bio-8080-exec-2" #20 daemon prio=5 os_prio=0 tid=0x0000000001aea000 nid=0x8de waiting on condition [0x00007fabd0abb000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
"http-bio-8080-exec-1" #19 daemon prio=5 os_prio=0 tid=0x0000000001ae8800 nid=0x8dd waiting on condition [0x00007fabd0bbc000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
"ajp-bio-8009-AsyncTimeout" #17 daemon prio=5 os_prio=0 tid=0x00007fabe8128000 nid=0x8d0 waiting on condition [0x00007fabd0ece000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:152)
at java.lang.Thread.run(Thread.java:748)
"ajp-bio-8009-Acceptor-0" #16 daemon prio=5 os_prio=0 tid=0x00007fabe82d4000 nid=0x8cf runnable [0x00007fabd0fcf000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)
at java.lang.Thread.run(Thread.java:748)
"http-bio-8080-AsyncTimeout" #15 daemon prio=5 os_prio=0 tid=0x00007fabe82d1800 nid=0x8ce waiting on condition [0x00007fabd10d0000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:152)
at java.lang.Thread.run(Thread.java:748)
"http-bio-8080-Acceptor-0" #14 daemon prio=5 os_prio=0 tid=0x00007fabe82d0000 nid=0x8cd runnable [0x00007fabd11d1000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)
at java.lang.Thread.run(Thread.java:748)
"ContainerBackgroundProcessor[StandardEngine[Catalina]]" #13 daemon prio=5 os_prio=0 tid=0x00007fabe82ce000 nid=0x8cc waiting on condition [0x00007fabd12d2000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1513)
at java.lang.Thread.run(Thread.java:748)
"GC Daemon" #10 daemon prio=2 os_prio=0 tid=0x00007fabe83b4000 nid=0x8b3 in Object.wait() [0x00007fabd1c2f000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000e315c2d0> (a sun.misc.GC$LatencyLock)
at sun.misc.GC$Daemon.run(GC.java:117)
- locked <0x00000000e315c2d0> (a sun.misc.GC$LatencyLock)
"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007fabe80c3800 nid=0x8a5 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fabe80b6800 nid=0x8a4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fabe80b3800 nid=0x8a3 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fabe80b2000 nid=0x8a2 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fabe807f000 nid=0x8a1 in Object.wait() [0x00007fabd2a67000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000e3162918> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x00000000e3162918> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fabe807a800 nid=0x8a0 in Object.wait() [0x00007fabd2b68000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000e3162958> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000e3162958> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"main" #1 prio=5 os_prio=0 tid=0x00007fabe8009000 nid=0x89c runnable [0x00007fabed210000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:453)
at org.apache.catalina.startup.Catalina.await(Catalina.java:777)
at org.apache.catalina.startup.Catalina.start(Catalina.java:723)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:321)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:455)
"VM Thread" os_prio=0 tid=0x00007fabe8073000 nid=0x89f runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fabe801e000 nid=0x89d runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fabe8020000 nid=0x89e runnable
"VM Periodic Task Thread" os_prio=0 tid=0x00007fabe80d6800 nid=0x8a6 waiting on condition
JNI global references: 43
6.1、線程的狀態
在Java中線程的狀態一共被分成6種:
-
初始態(NEW)
- 創建一個Thread對象,但還未調用start()啟動線程時,線程處于初始態。
-
運行態(RUNNABLE),在Java中,運行態包括 就緒態 和 運行態。
-
就緒態
- 該狀態下的線程已經獲得執行所需的所有資源,只要CPU分配執行權就能運行。
- 所有就緒態的線程存放在就緒隊列中。
-
運行態
- 獲得CPU執行權,正在執行的線程。
- 由于一個CPU同一時刻只能執行一條線程,因此每個CPU每個時刻只有一條運行態的線程。
-
-
阻塞態(BLOCKED)
- 當一條正在執行的線程請求某一資源失敗時,就會進入阻塞態。
- 而在Java中,阻塞態專指請求鎖失敗時進入的狀態。
- 由一個阻塞隊列存放所有阻塞態的線程。
- 處于阻塞態的線程會不斷請求資源,一旦請求成功,就會進入就緒隊列,等待執行。
-
等待態(WAITING)
- 當前線程中調用wait、join、park函數時,當前線程就會進入等待態。
- 也有一個等待隊列存放所有等待態的線程。
- 線程處于等待態表示它需要等待其他線程的指示才能繼續運行。
- 進入等待態的線程會釋放CPU執行權,并釋放資源(如:鎖)
-
超時等待態(TIMED_WAITING)
- 當運行中的線程調用sleep(time)、wait、join、parkNanos、parkUntil時,就會進入該狀態;
- 它和等待態一樣,并不是因為請求不到資源,而是主動進入,并且進入后需要其他線程喚醒;
- 進入該狀態后釋放CPU執行權 和 占有的資源。
- 與等待態的區別:到了超時時間后自動進入阻塞隊列,開始競爭鎖。
-
終止態(TERMINATED)
- 線程執行結束后的狀態。
6.2、實戰:死鎖問題
如果在生產環境發生了死鎖,我們將看到的是部署的程序沒有任何反應了,這個時候我們可以借助jstack進行分析,下面我們實戰下查找死鎖的原因。
6.2.1、構造死鎖
編寫代碼,啟動2個線程,Thread1拿到了obj1鎖,準備去拿obj2鎖時,obj2已經被Thread2鎖定,所以發送了死鎖。
public class TestDeadLock {
private static Object obj1 = new Object();
private static Object obj2 = new Object();
public static void main(String[] args) {
new Thread(new Thread1()).start();
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable{
@Override
public void run() {
synchronized (obj1){
System.out.println("Thread1 拿到了 obj1 的鎖!");
try {
// 停頓2秒的意義在于,讓Thread2線程拿到obj2的鎖
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println("Thread1 拿到了 obj2 的鎖!");
}
}
}
}
private static class Thread2 implements Runnable{
@Override
public void run() {
synchronized (obj2){
System.out.println("Thread2 拿到了 obj2 的鎖!");
try {
// 停頓2秒的意義在于,讓Thread1線程拿到obj1的鎖
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println("Thread2 拿到了 obj1 的鎖!");
}
}
}
}
}
6.2.2、在linux上運行
[root@node01 test]# javac TestDeadLock.java
[root@node01 test]# ll
總用量 28
-rw-r--r--. 1 root root 184 9月 11 10:39 TestDeadLock$1.class
-rw-r--r--. 1 root root 843 9月 11 10:39 TestDeadLock.class
-rw-r--r--. 1 root root 1567 9月 11 10:39 TestDeadLock.java
-rw-r--r--. 1 root root 1078 9月 11 10:39 TestDeadLock$Thread1.class
-rw-r--r--. 1 root root 1078 9月 11 10:39 TestDeadLock$Thread2.class
-rw-r--r--. 1 root root 573 9月 9 10:21 TestJVM.class
-rw-r--r--. 1 root root 261 9月 9 10:21 TestJVM.java
[root@node01 test]# java TestDeadLock
Thread1 拿到了 obj1 的鎖!
Thread2 拿到了 obj2 的鎖!
#這里發生了死鎖,程序一直將等待下去
6.2.3、使用jstack進行分析
[root@node01 ~]# jstack 3256
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.141-b15 mixed mode):
"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007f5bfc001000 nid=0xcff waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007f5c2c008800 nid=0xcb9 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #9 prio=5 os_prio=0 tid=0x00007f5c2c0e9000 nid=0xcc5 waiting for monitor entry [0x00007f5c1c7f6000]
java.lang.Thread.State: BLOCKED (on object monitor)
at TestDeadLock$Thread2.run(TestDeadLock.java:47)
- waiting to lock <0x00000000f655dc40> (a java.lang.Object)
- locked <0x00000000f655dc50> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #8 prio=5 os_prio=0 tid=0x00007f5c2c0e7000 nid=0xcc4 waiting for monitor entry [0x00007f5c1c8f7000]
java.lang.Thread.State: BLOCKED (on object monitor)
at TestDeadLock$Thread1.run(TestDeadLock.java:27)
- waiting to lock <0x00000000f655dc50> (a java.lang.Object)
- locked <0x00000000f655dc40> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f5c2c0d3000 nid=0xcc2 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f5c2c0b6000 nid=0xcc1 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f5c2c0b3000 nid=0xcc0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f5c2c0b1800 nid=0xcbf runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f5c2c07e800 nid=0xcbe in Object.wait() [0x00007f5c1cdfc000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000f6508ec8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x00000000f6508ec8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f5c2c07a000 nid=0xcbd in Object.wait() [0x00007f5c1cefd000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000f6506b68> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000f6506b68> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=0 tid=0x00007f5c2c072800 nid=0xcbc runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f5c2c01d800 nid=0xcba runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f5c2c01f800 nid=0xcbb runnable
"VM Periodic Task Thread" os_prio=0 tid=0x00007f5c2c0d6800 nid=0xcc3 waiting on condition
JNI global references: 6
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f5c080062c8 (object 0x00000000f655dc40, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007f5c08004e28 (object 0x00000000f655dc50, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at TestDeadLock$Thread2.run(TestDeadLock.java:47)
- waiting to lock <0x00000000f655dc40> (a java.lang.Object)
- locked <0x00000000f655dc50> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at TestDeadLock$Thread1.run(TestDeadLock.java:27)
- waiting to lock <0x00000000f655dc50> (a java.lang.Object)
- locked <0x00000000f655dc40> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
在輸出的信息中,已經看到,發現了1個死鎖,關鍵看這個:
"Thread-1":
at TestDeadLock$Thread2.run(TestDeadLock.java:47)
- waiting to lock <0x00000000f655dc40> (a java.lang.Object)
- locked <0x00000000f655dc50> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at TestDeadLock$Thread1.run(TestDeadLock.java:27)
- waiting to lock <0x00000000f655dc50> (a java.lang.Object)
- locked <0x00000000f655dc40> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
可以清晰的看到:
- Thread2獲取了 <0x00000000f655dc50> 的鎖,等待獲取 <0x00000000f655dc40> 這個鎖
- Thread1獲取了 <0x00000000f655dc40> 的鎖,等待獲取 <0x00000000f655dc50> 這個鎖
- 由此可見,發生了死鎖。
7、VisualVM工具的使用
VisualVM,能夠監控線程,內存情況,查看方法的CPU時間和內存中的對 象,已被GC的對象,反向查看分配的堆棧(如100個String對象分別由哪幾個對象分配出來的)。
VisualVM使用簡單,幾乎0配置,功能還是比較豐富的,幾乎囊括了其它JDK自帶命令的所有功能。
- 內存信息
- 線程信息
- Dump堆(本地進程)
- Dump線程(本地進程)
- 打開堆Dump。堆Dump可以用jmap來生成。
- 打開線程Dump
- 生成應用快照(包含內存信息、線程信息等等)
- 性能分析。CPU分析(各個方法調用時間,檢查哪些方法耗時多),內存分析(各類對象占用的內存,檢查哪些類占用內存多)
- ……
7.1、啟動
在jdk的安裝目錄的bin目錄下,找到jvisualvm.exe,雙擊打開即可。
7.2、查看本地進程
7.3、查看CPU、內存、類、線程運行信息
7.4、查看線程詳情
也可以點擊右上角Dump按鈕,將線程的信息導出,其實就是執行的jstack命令。
發現,顯示的內容是一樣的。
7.5、抽樣器
抽樣器可以對CPU、內存在一段時間內進行抽樣,以供分析。
7.6、監控遠程的jvm
VisualJVM不僅是可以監控本地jvm進程,還可以監控遠程的jvm進程,需要借助于JMX技術實現。
7.6.1、什么是JMX?
JMX(Java Management Extensions,即Java管理擴展)是一個為應用程序、設備、系統等植入管理功能的框架。JMX可以跨越一系列異構操作系統平臺、系統體系結構和網絡傳輸協議,靈活的開發無縫集成的系統、網絡和服務管理應用。
7.6.2、監控遠程的tomcat
想要監控遠程的tomcat,就需要在遠程的tomcat進行對JMX配置,方法如下:
#在tomcat的bin目錄下,修改catalina.sh,添加如下的參數
JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
#這幾個參數的意思是:
#-Dcom.sun.management.jmxremote :允許使用JMX遠程管理
#-Dcom.sun.management.jmxremote.port=9999 :JMX遠程連接端口
#-Dcom.sun.management.jmxremote.authenticate=false :不進行身份認證,任何用戶都可以連接
#-Dcom.sun.management.jmxremote.ssl=false :不使用ssl
保存退出,重啟tomcat。
7.6.3、使用VisualJVM連接遠程tomcat
添加遠程主機:
在一個主機下可能會有很多的jvm需要監控,所以接下來要在該主機上添加需要監控的jvm:
連接成功。使用方法和前面就一樣了,就可以和監控本地jvm進程一樣,監控遠程的tomcat進程。