JVM優化原理及基本知識

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/ 進行訪問

訪問成功:
1536488631901.png
#查看所有的參數,用法: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的堆內存模型

1536501289198.png
  • 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的堆內存模型

1536501652150.png

由上圖可以看出,jdk1.8的內存模型是由2部分組成,年輕代 + 年老代。

年輕代:Eden + 2*Survivor

年老代:OldGen

在jdk1.8中變化最大的Perm區,用Metaspace(元數據空間)進行了替換。

需要特別說明的是:Metaspace所占用的內存空間不是在虛擬機內部,而是在本地內存空間中,這也是與1.7的永久代最大的區別所在。

1536507420882.png

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

1536512769514.png

可以看到已經在/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/

1536513416186.png

在最后面有OQL查詢功能。

1536513474958.png
1536513591638.png

4.5、通過MAT工具對dump文件進行分析

4.5.1、MAT工具介紹

MAT(Memory Analyzer Tool),一個基于Eclipse的內存分析工具,是一個快速、功能豐富的JAVA heap分析工具,它可以幫助我們查找內存泄漏和減少內存消耗。使用內存分析工具從眾多的對象中進行分析,快速的計算出在內存中對象的占用大小,看看是誰阻止了垃圾收集器的回收工作,并可以通過報表直觀的查看到可能造成這種結果的對象。

官網地址: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進行解壓:

1536515524838.png

4.5.3、使用

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

查看對象以及它的依賴:


1536516171924.png

查看可能存在內存泄露的分析:


1536516259369.png

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編輯器。

1536591244437.png
#參數如下:
-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。


1536591747657.png

5.3、導入到MAT工具中進行分析

1536592135948.png

可以看到,有91.03%的內存由Object[]數組占有,所以比較可疑。

分析:這個可疑是正確的,因為已經有超過90%的內存都被它占有,這是非常有可能出現內存溢出的。

查看詳情:


1536594010544.png

可以看到集合中存儲了大量的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、線程的狀態

1536601656694.png

在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,雙擊打開即可。

1536654243155.png
1536654347046.png

7.2、查看本地進程

1536654611922.png

7.3、查看CPU、內存、類、線程運行信息

1536654708421.png

7.4、查看線程詳情

1536654784904.png

也可以點擊右上角Dump按鈕,將線程的信息導出,其實就是執行的jstack命令。

1536656468818.png

發現,顯示的內容是一樣的。

7.5、抽樣器

抽樣器可以對CPU、內存在一段時間內進行抽樣,以供分析。


1536678677028.png

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

添加遠程主機:

1536681377182.png

在一個主機下可能會有很多的jvm需要監控,所以接下來要在該主機上添加需要監控的jvm:

1536681543452.png
1536681575620.png

連接成功。使用方法和前面就一樣了,就可以和監控本地jvm進程一樣,監控遠程的tomcat進程。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375