JVM調(diào)優(yōu)(一)

jvm.jpg

JVM調(diào)優(yōu)(一)

本片內(nèi)容

  • 我們?yōu)槭裁匆獙VM優(yōu)化
  • 掌握jvm的運(yùn)行參數(shù)以及參數(shù)的設(shè)置
  • 掌握jvm的內(nèi)存模型(堆內(nèi)存)
  • 掌握jamp命令的使用以及通過MAT工具進(jìn)行分析
  • 掌握定位分析內(nèi)存溢出的方法
  • 掌握jstack命令的使用
  • 掌握VisualJVM工具的使用

1、我們?yōu)槭裁匆獙vm做優(yōu)化?

在本地開發(fā)環(huán)境中我們很少會遇到需要對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。

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版本中不會改變,可以使用java -help檢索出所有的標(biāo)準(zhǔn)參數(shù)。

[root@node01 ~]# java -help
用法: java [-options] class [args...]
           (執(zhí)行類)
   或  java [-options] -jar jarfile [args...]
           (執(zhí)行 jar 文件)
其中選項(xiàng)包括:
    -d32      使用 32 位數(shù)據(jù)模型 (如果可用)
    -d64      使用 64 位數(shù)據(jù)模型 (如果可用)
    -server   選擇 "server" VM
                  默認(rèn) VM 是 server,
                  因?yàn)槟窃诜?wù)器類計(jì)算機(jī)上運(yùn)行。


    -cp <目錄和 zip/jar 文件的類搜索路徑>
    -classpath <目錄和 zip/jar 文件的類搜索路徑>
                  用 : 分隔的目錄, JAR 檔案
                  和 ZIP 檔案列表, 用于搜索類文件。
    -D<名稱>=<值>
                  設(shè)置系統(tǒng)屬性
    -verbose:[class|gc|jni]
                  啟用詳細(xì)輸出
    -version      輸出產(chǎn)品版本并退出
    -version:<值>
                  警告: 此功能已過時(shí), 將在
                  未來發(fā)行版中刪除。
                  需要指定的版本才能運(yùn)行
    -showversion  輸出產(chǎn)品版本并繼續(xù)
    -jre-restrict-search | -no-jre-restrict-search
                  警告: 此功能已過時(shí), 將在
                  未來發(fā)行版中刪除。
                  在版本搜索中包括/排除用戶專用 JRE
    -? -help      輸出此幫助消息
    -X            輸出非標(biāo)準(zhǔn)選項(xiàng)的幫助
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  按指定的粒度啟用斷言
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  禁用具有指定粒度的斷言
    -esa | -enablesystemassertions
                  啟用系統(tǒng)斷言
    -dsa | -disablesystemassertions
                  禁用系統(tǒng)斷言
    -agentlib:<libname>[=<選項(xiàng)>]
                  加載本機(jī)代理庫 <libname>, 例如 -agentlib:hprof
                  另請參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help
    -agentpath:<pathname>[=<選項(xiàng)>]
                  按完整路徑名加載本機(jī)代理庫
    -javaagent:<jarpath>[=<選項(xiàng)>]
                  加載 Java 編程語言代理, 請參閱 java.lang.instrument
    -splash:<imagepath>
                  使用指定的圖像顯示啟動屏幕

2.2.1、實(shí)戰(zhàn)

實(shí)戰(zhàn)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參數(shù)是表示,先打印版本信息,再執(zhí)行后面的命令,在調(diào)試時(shí)非常有用,后面會使用到。

實(shí)戰(zhàn)2:通過-D設(shè)置系統(tǒng)屬性參數(shù)


public class TestJVM {

    public static void main(String[] args) {
        String str = System.getProperty("str");
        if (str == null) {
            System.out.println("sufeng");
        } else {
            System.out.println(str);
        }
    }
}

進(jìn)行編譯、測試:

#編譯
[root@node01 test]# javac TestJVM.java

#測試
[root@node01 test]# java TestJVM
sufeng
[root@node01 test]# java -Dstr=123 TestJVM
123

2.2.2、-server與-client參數(shù)

可以通過-server或-client設(shè)置jvm的運(yùn)行參數(shù)。

  • 它們的區(qū)別是Server VM的初始堆空間會大一些,默認(rèn)使用的是并行垃圾回收器,啟動慢運(yùn)行快。
  • Client VM相對來講會保守一些,初始堆空間會小一些,使用串行的垃圾回收器,它的目標(biāo)是為了讓JVM的啟動速度更快,但運(yùn)行速度會比Server模式慢些。
  • JVM在啟動的時(shí)候會根據(jù)硬件和操作系統(tǒ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類型。

測試:

[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)

sufeng

[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)

sufeng

#由于機(jī)器是64位系統(tǒng),所以不支持client模式

2.3、-X參數(shù)

jvm的-X參數(shù)是非標(biāo)準(zhǔn)參數(shù),在不同版本的jvm中,參數(shù)可能會有所不同,可以通過java -X查看非標(biāo)準(zhǔn)參數(shù)。

[root@node01 test]# 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           禁用后臺編譯
    -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)記會強(qiáng)制JVM執(zhí)行所有的字節(jié)碼,當(dāng)然這會降低運(yùn)行速度,通常低10倍或更多。
  • -Xcomp參數(shù)與它(-Xint)正好相反,JVM在第一次使用時(shí)會把所有的字節(jié)碼編譯成本地代碼,從而帶來最大程度的優(yōu)化。
    • 然而,很多應(yīng)用在使用-Xcomp也會有一些性能損失,當(dāng)然這比使用-Xint損失的少,原因是-xcomp沒有讓JVM啟用JIT編譯器的全部功能。JIT編譯器可以對是否需要編譯做判斷,如果所有代碼都進(jìn)行編譯的話,對于一些只執(zhí)行一次的代碼就沒有意義了。
  • -Xmixed是混合模式,將解釋模式與編譯模式進(jìn)行混合使用,由jvm自己決定,這是jvm默認(rèn)的模式,也是推薦使用的模式。

示例:強(qiáng)制設(shè)置運(yùn)行模式

#強(qiáng)制設(shè)置為解釋模式
[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)

sufeng


#強(qiáng)制設(shè)置為編譯模式
[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)

sufeng
#注意:編譯模式下,第一次執(zhí)行會比解釋模式下執(zhí)行慢一些,注意觀察。


#默認(rèn)的混合模式
[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)

sufeng

2.4、-XX參數(shù)

-XX參數(shù)也是非標(biāo)準(zhǔn)參數(shù),主要用于jvm的調(diào)優(yōu)和debug操作。

-XX參數(shù)的使用有2種方式,一種是boolean類型,一種是非boolean類型:

  • boolean類型
    • 格式:-XX:[+-]<name> 表示啟用或禁用<name>屬性
    • 如:-XX:+DisableExplicitGC 表示禁用手動調(diào)用gc操作,也就是說調(diào)用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)

sufeng

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ù)器資源,讓程序跑的更快。

示例:

[root@node01 test]# java -Xms512m -Xmx2048m TestJVM
sufeng

2.6、查看jvm的運(yùn)行參數(shù)

有些時(shí)候我們需要查看jvm的運(yùn)行參數(shù),這個(gè)需求可能會存在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ù)即可。

[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)

由上述的信息可以看出,參數(shù)有boolean類型和數(shù)字類型,值的操作符是=或:=,分別代表默認(rèn)值和被修改的值。

示例:

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這個(gè)參數(shù)已經(jīng)被修改了。

2.6.2、查看正在運(yùn)行的jvm參數(shù)

如果想要查看正在運(yùn)行的jvm就需要借助于jinfo命令查看。

首先,啟動一個(gè)tomcat用于測試,來觀察下運(yùn)行的jvm參數(shù)。

 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/ 進(jìn)行訪問

訪問成功:
tomcat.png
#查看所有的參數(shù),用法:jinfo -flags <進(jìn)程id>

#通過jps 或者  jps -l 查看java進(jìn)程
[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

#查看某一參數(shù)的值,用法:jinfo -flag <參數(shù)名> <進(jìn)程id>
[root@node01 bin]# jinfo -flag MaxHeapSize 6219
-XX:MaxHeapSize=488636416

3、jvm的內(nèi)存模型

jvm的內(nèi)存模型在1.7和1.8有較大的區(qū)別,下面將先介紹1.7再學(xué)習(xí)1.8的內(nèi)存模型。

3.1、jdk1.7的堆內(nèi)存模型

jdk1.7.png
  • Young 年輕區(qū)(代)

    Young區(qū)被劃分為三部分,Eden區(qū)和兩個(gè)大小嚴(yán)格相同的Survivor區(qū),其中,Survivor區(qū)間中,某一時(shí)刻只有其中一個(gè)是被使用的,另外一個(gè)留做垃圾收集時(shí)復(fù)制對象用,在Eden區(qū)間變滿的時(shí)候, GC就會將存活的對象移到空閑的Survivor區(qū)間中,根據(jù)JVM的策略,在經(jīng)過幾次垃圾收集后,任然存活于Survivor的對象將被移動到Tenured區(qū)間。

  • Tenured 年老區(qū)

    Tenured區(qū)主要保存生命周期長的對象,一般是一些老的對象,當(dāng)一些對象在Young復(fù)制轉(zhuǎn)移一定的次數(shù)以后,對象就會被轉(zhuǎn)移到Tenured區(qū),一般如果系統(tǒng)中用了application級別的緩存,緩存中的對象往往會被轉(zhuǎn)移到這一區(qū)間。

  • Perm 永久區(qū)

    Perm代主要保存class,method,filed對象,這部份的空間一般不會溢出,除非一次性加載了很多的類,不過在涉及到熱部署的應(yīng)用服務(wù)器的時(shí)候,有時(shí)候會遇到j(luò)ava.lang.OutOfMemoryError : PermGen space 的錯(cuò)誤,造成這個(gè)錯(cuò)誤的很大原因就有可能是每次都重新部署,但是重新部署后,類的class沒有被卸載掉,這樣就造成了大量的class對象保存在了perm中,這種情況下,一般重新啟動應(yīng)用服務(wù)器可以解決問題。

  • Virtual區(qū):

    • 最大內(nèi)存和初始內(nèi)存的差值,就是Virtual區(qū)。

3.2、jdk1.8的堆內(nèi)存模型

jdk1.8.png

由上圖可以看出,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ū)別所在。

jdk1.8.png

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而做出的努力,因
JRockit沒有永久代,不需要配置永久代。

現(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ì)

[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的數(shù)量
  • Bytes:所占用空間大小
  • Unloaded:未加載數(shù)量
  • Bytes:未加載占用空間
  • Time:時(shí)間

3.4.2、查看編譯統(tǒng)計(jì)

[root@node01 ~]# jstat -compiler 6219
Compiled Failed Invalid   Time   FailedType FailedMethod
    2376      1       0     8.04          1 org/apache/tomcat/util/IntrospectionUtils setProperty

說明:

  • Compiled:編譯數(shù)量。
  • Failed:失敗數(shù)量
  • Invalid:不可用數(shù)量
  • Time:時(shí)間
  • FailedType:失敗類型
  • FailedMethod:失敗的方法

3.4.3、垃圾回收統(tǒng)計(jì)

[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

#也可以指定打印的間隔和次數(shù),每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:第一個(gè)Survivor區(qū)的大?。↘B)
  • S1C:第二個(gè)Survivor區(qū)的大?。↘B)
  • S0U:第一個(gè)Survivor區(qū)的使用大?。↘B)
  • S1U:第二個(gè)Survivor區(qū)的使用大?。↘B)
  • EC:Eden區(qū)的大?。↘B)
  • EU:Eden區(qū)的使用大?。↘B)
  • OC:Old區(qū)大?。↘B)
  • OU:Old使用大?。↘B)
  • MC:方法區(qū)大?。↘B)
  • MU:方法區(qū)使用大?。↘B)
  • CCSC:壓縮類空間大?。↘B)
  • CCSU:壓縮類空間使用大?。↘B)
  • YGC:年輕代垃圾回收次數(shù)
  • YGCT:年輕代垃圾回收消耗時(shí)間
  • 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)存使用情況

[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: #堆內(nèi)存配置信息
   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: # 堆內(nèi)存的使用情況
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、查看內(nèi)存中對象數(shù)量及大小

#查看所有對象,包括活躍以及非活躍的
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
[  數(shù)組,如[I表示int[]
[L+類名 其他對象

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=/tmp/dump.dat 6219

dump.png

可以看到已經(jīng)在/tmp下生成了dump.dat的文件。

4.4、通過jhat對dump文件進(jìn)行分析

在上一小節(jié)中,我們將jvm的內(nèi)存dump到文件中,這個(gè)文件是一個(gè)二進(jìn)制的文件,不方便查看,這時(shí)我們可以借助于jhat工具進(jìn)行查看。

#用法:
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.

打開瀏覽器進(jìn)行訪問:http://192.168.40.133:9999/

jhat-index.png

在最后面有OQL查詢功能。

OQL1.png
OQL2.png

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/

1536515331814.png

4.5.2、下載安裝

下載地址:https://www.eclipse.org/mat/downloads.php

1536515395914.png

將下載得到的MemoryAnalyzer-1.8.0.20180604-win32.win32.x86_64.zip進(jìn)行解壓:

1536515524838.png

4.5.3、使用

1536515700264.png
1536515764096.png
1536515922938.png
1536516001249.png

查看對象以及它的依賴:

1536516171924.png

查看可能存在內(nèi)存泄露的分析:


1536516259369.png

5、實(shí)戰(zhàn):內(nèi)存溢出的定位與分析

內(nèi)存溢出在實(shí)際的生產(chǎn)環(huán)境中經(jīng)常會遇到,比如,不斷的將數(shù)據(jù)寫入到一個(gè)集合中,出現(xiàn)了死循環(huán),讀取超大的文件等等,都可能會造成內(nèi)存溢出。

如果出現(xiàn)了內(nèi)存溢出,首先我們需要定位到發(fā)生內(nèi)存溢出的環(huán)節(jié),并且進(jìn)行分析,是正常還是非正常情況,如果是正常的需求,就應(yīng)該考慮加大內(nèi)存的設(shè)置,如果是非正常需求,那么就要對代碼進(jìn)行修改,修復(fù)這個(gè)bug。

首先,我們得先學(xué)會如何定位問題,然后再進(jìn)行分析。如何定位問題呢,我們需要借助于jmap與MAT工具進(jìn)行定位分析。

接下來,我們模擬內(nèi)存溢出的場景。

5.1、模擬內(nèi)存溢出

編寫代碼,向List集合中添加100萬個(gè)字符串,每個(gè)字符串由1000個(gè)UUID組成。如果程序能夠正常執(zhí)行,最后打印ok。

package cn.sufeng.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");
    }
}

為了演示效果,我們將設(shè)置執(zhí)行的參數(shù),這里使用的是Idea編輯器。

1536591244437.png
#參數(shù)如下:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

5.2、運(yùn)行測試

測試結(jié)果如下:

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.itcast.jvm.TestJvmOutOfMemory.main(TestJvmOutOfMemory.java:14)

Process finished with exit code 1

可以看到,當(dāng)發(fā)生內(nèi)存溢出時(shí),會dump文件到j(luò)ava_pid5348.hprof。


1536591747657.png

5.3、導(dǎo)入到MAT工具中進(jìn)行分析

1536592135948.png

可以看到,有91.03%的內(nèi)存由Object[]數(shù)組占有,所以比較可疑。

分析:這個(gè)可疑是正確的,因?yàn)橐呀?jīng)有超過90%的內(nèi)存都被它占有,這是非常有可能出現(xiàn)內(nèi)存溢出的。

查看詳情:

1536594010544.png

可以看到集合中存儲了大量的uuid字符串。

6、jstack的使用

有些時(shí)候我們需要查看下jvm中的線程執(zhí)行情況,比如,發(fā)現(xiàn)服務(wù)器的CPU的負(fù)載突然增高了、出現(xiàn)了死鎖、死循環(huán)等,我們該如何分析呢?

由于程序是正常運(yùn)行的,沒有任何的輸出,從日志方面也看不出什么問題,所以就需要看下jvm的內(nèi)部線程的執(zhí)行情況,然后再進(jìn)行分析查找出原因。

這個(gè)時(shí)候,就需要借助于jstack命令了,jstack的作用是將正在運(yùn)行的jvm的線程情況進(jìn)行快照,并且打印出來:

#用法: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、線程的狀態(tài)

1536600198133.png
1536601656694.png

在Java中線程的狀態(tài)一共被分成6種:

  • 初始態(tài)(NEW)

    • 創(chuàng)建一個(gè)Thread對象,但還未調(diào)用start()啟動線程時(shí),線程處于初始態(tài)。
  • 運(yùn)行態(tài)(RUNNABLE),在Java中,運(yùn)行態(tài)包括 就緒態(tài) 和 運(yùn)行態(tài)。

    • 就緒態(tài)

      • 該狀態(tài)下的線程已經(jīng)獲得執(zhí)行所需的所有資源,只要CPU分配執(zhí)行權(quán)就能運(yùn)行。
      • 所有就緒態(tài)的線程存放在就緒隊(duì)列中。
    • 運(yùn)行態(tài)

      • 獲得CPU執(zhí)行權(quán),正在執(zhí)行的線程。
      • 由于一個(gè)CPU同一時(shí)刻只能執(zhí)行一條線程,因此每個(gè)CPU每個(gè)時(shí)刻只有一條運(yùn)行態(tài)的線程。
  • 阻塞態(tài)(BLOCKED)

    • 當(dāng)一條正在執(zhí)行的線程請求某一資源失敗時(shí),就會進(jìn)入阻塞態(tài)。
    • 而在Java中,阻塞態(tài)專指請求鎖失敗時(shí)進(jìn)入的狀態(tài)。
    • 由一個(gè)阻塞隊(duì)列存放所有阻塞態(tài)的線程。
    • 處于阻塞態(tài)的線程會不斷請求資源,一旦請求成功,就會進(jìn)入就緒隊(duì)列,等待執(zhí)行。
  • 等待態(tài)(WAITING)

    • 當(dāng)前線程中調(diào)用wait、join、park函數(shù)時(shí),當(dāng)前線程就會進(jìn)入等待態(tài)。
    • 也有一個(gè)等待隊(duì)列存放所有等待態(tài)的線程。
    • 線程處于等待態(tài)表示它需要等待其他線程的指示才能繼續(xù)運(yùn)行。
    • 進(jìn)入等待態(tài)的線程會釋放CPU執(zhí)行權(quán),并釋放資源(如:鎖)
  • 超時(shí)等待態(tài)(TIMED_WAITING)

    • 當(dāng)運(yùn)行中的線程調(diào)用sleep(time)、wait、join、parkNanos、parkUntil時(shí),就會進(jìn)入該狀態(tài);
    • 它和等待態(tài)一樣,并不是因?yàn)檎埱蟛坏劫Y源,而是主動進(jìn)入,并且進(jìn)入后需要其他線程喚醒;
    • 進(jìn)入該狀態(tài)后釋放CPU執(zhí)行權(quán) 和 占有的資源。
    • 與等待態(tài)的區(qū)別:到了超時(shí)時(shí)間后自動進(jìn)入阻塞隊(duì)列,開始競爭鎖。
  • 終止態(tài)(TERMINATED)

    • 線程執(zhí)行結(jié)束后的狀態(tài)。

6.2、實(shí)戰(zhàn):死鎖問題

如果在生產(chǎn)環(huán)境發(fā)生了死鎖,我們將看到的是部署的程序沒有任何反應(yīng)了,這個(gè)時(shí)候我們可以借助jstack進(jìn)行分析,下面我們實(shí)戰(zhàn)下查找死鎖的原因。

6.2.1、構(gòu)造死鎖

編寫代碼,啟動2個(gè)線程,Thread1拿到了obj1鎖,準(zhǔn)備去拿obj2鎖時(shí),obj2已經(jīng)被Thread2鎖定,所以發(fā)送了死鎖。

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上運(yùn)行

[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 的鎖!
#這里發(fā)生了死鎖,程序一直將等待下去

6.2.3、使用jstack進(jìn)行分析

[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.

在輸出的信息中,已經(jīng)看到,發(fā)現(xiàn)了1個(gè)死鎖,關(guān)鍵看這個(gè):

"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> 這個(gè)鎖
  • Thread1獲取了 <0x00000000f655dc40> 的鎖,等待獲取 <0x00000000f655dc50> 這個(gè)鎖
  • 由此可見,發(fā)生了死鎖。

7、VisualVM工具的使用

VisualVM,能夠監(jiān)控線程,內(nèi)存情況,查看方法的CPU時(shí)間和內(nèi)存中的對 象,已被GC的對象,反向查看分配的堆棧(如100個(gè)String對象分別由哪幾個(gè)對象分配出來的)。

VisualVM使用簡單,幾乎0配置,功能還是比較豐富的,幾乎囊括了其它JDK自帶命令的所有功能。

  • 內(nèi)存信息
  • 線程信息
  • Dump堆(本地進(jìn)程)
  • Dump線程(本地進(jìn)程)
  • 打開堆Dump。堆Dump可以用jmap來生成。
  • 打開線程Dump
  • 生成應(yīng)用快照(包含內(nèi)存信息、線程信息等等)
  • 性能分析。CPU分析(各個(gè)方法調(diào)用時(shí)間,檢查哪些方法耗時(shí)多),內(nèi)存分析(各類對象占用的內(nèi)存,檢查哪些類占用內(nèi)存多)
  • ……

7.1、啟動

在jdk的安裝目錄的bin目錄下,找到j(luò)visualvm.exe,雙擊打開即可。

1536654243155.png

7.2、查看本地進(jìn)程

1536654347046.png

7.3、查看CPU、內(nèi)存、類、線程運(yùn)行信息

1536654611922.png
1536654708421.png

7.4、查看線程詳情

1536654784904.png

也可以點(diǎn)擊右上角Dump按鈕,將線程的信息導(dǎo)出,其實(shí)就是執(zhí)行的jstack命令。

1536656468818.png

發(fā)現(xiàn),顯示的內(nèi)容是一樣的。

7.5、抽樣器

抽樣器可以對CPU、內(nèi)存在一段時(shí)間內(nèi)進(jìn)行抽樣,以供分析。

1536678677028.png

7.6、監(jiān)控遠(yuǎn)程的jvm

VisualJVM不僅是可以監(jiān)控本地jvm進(jìn)程,還可以監(jiān)控遠(yuǎn)程的jvm進(jìn)程,需要借助于JMX技術(shù)實(shí)現(xiàn)。

7.6.1、什么是JMX?

JMX(Java Management Extensions,即Java管理擴(kuò)展)是一個(gè)為應(yīng)用程序、設(shè)備、系統(tǒng)等植入管理功能的框架。JMX可以跨越一系列異構(gòu)操作系統(tǒng)平臺、系統(tǒng)體系結(jié)構(gòu)和網(wǎng)絡(luò)傳輸協(xié)議,靈活的開發(fā)無縫集成的系統(tǒng)、網(wǎng)絡(luò)和服務(wù)管理應(yīng)用。

7.6.2、監(jiān)控遠(yuǎn)程的tomcat

想要監(jiān)控遠(yuǎn)程的tomcat,就需要在遠(yuǎn)程的tomcat進(jìn)行對JMX配置,方法如下:

#在tomcat的bin目錄下,修改catalina.sh,添加如下的參數(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"

#這幾個(gè)參數(shù)的意思是:
#-Dcom.sun.management.jmxremote :允許使用JMX遠(yuǎn)程管理
#-Dcom.sun.management.jmxremote.port=9999 :JMX遠(yuǎn)程連接端口
#-Dcom.sun.management.jmxremote.authenticate=false :不進(jìn)行身份認(rèn)證,任何用戶都可以連接
#-Dcom.sun.management.jmxremote.ssl=false :不使用ssl

保存退出,重啟tomcat。

7.6.3、使用VisualJVM連接遠(yuǎn)程tomcat

添加遠(yuǎn)程主機(jī):

1536681377182.png

在一個(gè)主機(jī)下可能會有很多的jvm需要監(jiān)控,所以接下來要在該主機(jī)上添加需要監(jiān)控的jvm:

1536681543452.png

連接成功。使用方法和前面就一樣了,就可以和監(jiān)控本地jvm進(jìn)程一樣,監(jiān)控遠(yuǎn)程的tomcat進(jìn)程。


1536681575620.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容