天天看點

jvm優化-a第一節

第一節

1 為什麼要對jvm做優化

本地開發環境中很少會對jvm優化:
運作應用“卡住了”,日志不輸出,程式沒有反應
伺服器CPU負載突然變高
在多線程應用下如何配置設定線程數量
...
           

2 jvm的運作參數

jvm中有很多參數可以進行設定,絕大多參數保持預設設定即可。
           

2.1 三種參數類型

jvm的參數分為三種類型,分别是
· 标準參數
	-help
	-version
· -X參數(非标準參數)
	-Xint
	-Xcomp
· -XX參數(使用頻率較高)
	-XX:newSize
	-XX:+UserSerialGC
           

2.2 标準參數

  • jvm的标準參數,一般都是很穩定的,在未來的jvm版本中不會改變,可以使用java-help檢索出所有的标準參數

    [[email protected] ~]# java -help

    Usage: java [-options] class [args…]

    (to execute a class)

    or java [-options] -jar jarfile [args…]

    (to execute a jar file)

    where options include:

    -d32 use a 32-bit data model if available

    -d64 use a 64-bit data model if available

    -server to select the “server” VM

    The default VM is server.

    -cp <class search path of directories and zip/jar files>
      -classpath <class search path of directories and zip/jar files>
                    A : separated list of directories, JAR archives,
                    and ZIP archives to search for class files.
      -D<name>=<value>
                    set a system property
      -verbose:[class|gc|jni]
                    enable verbose output
      -version      print product version and exit
      -version:<value>
                    Warning: this feature is deprecated and will be removed
                    in a future release.
                    require the specified version to run
      -showversion  print product version and continue
      -jre-restrict-search | -no-jre-restrict-search
                    Warning: this feature is deprecated and will be removed
                    in a future release.
                    include/exclude user private JREs in the version search
      -? -help      print this help message
      -X            print help on non-standard options
      -ea[:<packagename>...|:<classname>]
      -enableassertions[:<packagename>...|:<classname>]
                    enable assertions with specified granularity
      -da[:<packagename>...|:<classname>]
      -disableassertions[:<packagename>...|:<classname>]
                    disable assertions with specified granularity
      -esa | -enablesystemassertions
                    enable system assertions
      -dsa | -disablesystemassertions
                    disable system assertions
      -agentlib:<libname>[=<options>]
                    load native agent library <libname>, e.g. -agentlib:hprof
                    see also, -agentlib:jdwp=help and -agentlib:hprof=help
      -agentpath:<pathname>[=<options>]
                    load native agent library by full pathname
      -javaagent:<jarpath>[=<options>]
                    load Java programming language agent, see java.lang.instrument
      -splash:<imagepath>
                    show splash screen with specified image
               
    See http://www.oracle.com/technetwork/java/javase/documentation/index.html for more details.

2.2.1 實戰

實戰1 檢視jvm版本

[[email protected] ~]# java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

# -showversion 參數是表示列印版本資訊,再執行後面的指令,在調試的時候非常有用,後面會使用到。
           

實戰2 通過-D設定系統參數屬性

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

進行編譯、測試

[[email protected] jvm]# javac ATestJVM.java
[[email protected] jvm]# 
[[email protected] jvm]# java ATestJVM
lifeng
[[email protected] jvm]# java -Dstr=12345 ATestJVM
12345
           

2.2.2 -server與-client參數

可以通過-server或-client設定jvm的運作參數

  • 他們的差別是Server VM初始堆空間會大一些,預設使用的是并行垃圾回收器,啟動慢運作快。
  • Client VM相對來講會保守一些,初始堆空間會小一些,使用穿行的垃圾回收器,他的目标是為了讓JVM的啟動速度更快,但運作速度會比Server VM模式慢些。
  • JVM在啟動的時候會根據硬體系統自動選擇使用Server還是Client類型的VM
  • 32位作業系統
    • 如果是Windows系統,不論硬體配置如何而,都預設使用Client類型的JVM
    • 如果是其他作業系統,機器配置有2GB以上的記憶體,同時有兩個以上CPU的話預設使用server模式,否則使用client模式
  • 64 位作業系統
    • 隻有server類型,不支援client類型

測試:

[[email protected] jvm]# java -client -showversion ATestJVM

java version “1.8.0_60”

Java™ SE Runtime Environment (build 1.8.0_60-b27)

Java HotSpot™ 64-Bit Server VM (build 25.60-b23, mixed mode)

lifeng
[[email protected] jvm]# 
[[email protected] jvm]# java -server -showversion ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

lifeng

# 機器是64位作業系統,是以不支援client模式
           

2.3 -X參數

jvm的-X參數是非标準的,在不同版本的jvm中,參數可能會有所不同,可以通過java -X檢視非标準參數

[[email protected] jvm]# java -X
    -Xmixed           mixed mode execution (default)
    -Xint             interpreted mode execution only  (僅解釋模式執行)
    -Xbootclasspath:<directories and zip/jar files separated by :>
                      set search path for bootstrap classes and resources
    -Xbootclasspath/a:<directories and zip/jar files separated by :>
                      append to end of bootstrap class path
    -Xbootclasspath/p:<directories and zip/jar files separated by :>
                      prepend in front of bootstrap class path
    -Xdiag            show additional diagnostic messages
    -Xnoclassgc       disable class garbage collection
    -Xincgc           enable incremental garbage collection
    -Xloggc:<file>    log GC status to a file with time stamps
    -Xbatch           disable background compilation
    -Xms<size>        set initial Java heap size
    -Xmx<size>        set maximum Java heap size
    -Xss<size>        set java thread stack size
    -Xprof            output cpu profiling data
    -Xfuture          enable strictest checks, anticipating future default
    -Xrs              reduce use of OS signals by Java/VM (see documentation)
    -Xcheck:jni       perform additional checks for JNI functions
    -Xshare:off       do not attempt to use shared class data
    -Xshare:auto      use shared class data if possible (default)
    -Xshare:on        require using shared class data, otherwise fail.
    -XshowSettings    show all settings and continue
    -XshowSettings:all
                      show all settings and continue
    -XshowSettings:vm show all vm related settings and continue
    -XshowSettings:properties
                      show all property settings and continue
    -XshowSettings:locale
                      show all locale related settings and continue

The -X options are non-standard and subject to change without notice.
           

2.3.1 -Xint、-Xcomp、-Xmixed

  • 在解釋模式(interpreted mode)下,-Xint标記會強制JVM執行所有的位元組碼,當然這會降低速度,通常低10倍或更多。
  • -Xcomp參數與(-Xint)參數正好相反,JVM在第一次使用時會把所有的位元組碼變異成本地代碼,進而帶來最大程度優化
    • 然而,很多應用在使用-Xcomp也會有一些性能損失,當然這比使用-Xint損失少,原因是-Xcomp沒有讓JVM啟用JIT編譯器的全部功能。JIT比阿尼一起可以對是否需要編譯做判斷,如果所有代碼都進行編譯的話,對于一些隻執行一次的代碼就沒有意義了。
  • -Xmixed是混合模式,将解釋模式與編譯模式進行混合使用,由jvm自己決定,這是jvm預設的模式,也是推薦使用的模式。

示例:強制設定為解釋模式

[[email protected] jvm]# java -showversion -Xint ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, interpreted mode)

lifeng
           

示例:強制設定為編譯模式

[[email protected] jvm]# java -showversion -Xcomp ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, compiled mode)

lifeng

#在編譯模式下,第一次執行會比解釋模式慢一些。
           

示例:預設的混合模式

[[email protected] jvm]# java -showversion ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

lifeng
           

2.4 -XX參數

-XX參數也是非标準參數,主要用于jvm的調優和debug操作。

-XX參數使用的兩種方式,一種是boolean類型,一種是非boolean類型

  • boolean類型
    • 格式:-XX:[±] 表示啟用或禁用屬性
    • 如: -XX:+DisableExplicitGC表示禁用手動調用gc操作,也就是說調用System.gc()無效
  • 非bollean類型
    • 格式:-XX:=表示的屬性值為
    • 如:-XX:NewRatio=1 表示新生代和老年代的比值

用法:

[[email protected] jvm]# java -showversion -XX:+DisableExplicitGC ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

lifeng
           

2.5 -Xms與-Xmx參數

-Xms與-Xmx分别是設定jvm的堆記憶體的初始大小和最大大小
-Xmx2048: 等價于-XX:MAXHeapSize,設定JVM最大堆記憶體為2048M
-Xms512:等價于-XX:InitialHeapSize,設定JVM初始堆記憶體為512M
适當的調整jvm的記憶體大小,可以充分利用伺服器資源,讓程式跑的更快。
           

示例:

[[email protected] jvm]# java -Xms512 -Xmx1024 ATestJVM 
Error occurred during initialization of VM
Too small initial heap
[[email protected] jvm]# java -Xms512m -Xmx1024m ATestJVM   
lifeng
[[email protected] jvm]# 
           

2.6 檢視jvm的運作參數

有些時候需要檢視jvm的運作參數,
第一,運作java指令時候列印出運作參數
第二,檢視正在運作的java程序的參數。
           

2.6.1 運作java指令時列印參數

添加 -XX:+PrintFlagsFinal參數即可。
	java -XX:+PrintFlagsFinal -version
# 可以看出參數有boolean類型和數字類型,值的操作符是=或者:=分别表示預設值和被修改的值。
           

2.6.2 檢視正在運作的jvm參數

需要借助于jinfo指令檢視
啟動一個tomcat,測試,用來觀察jvm參數:
啟動tomcat,通路8080端口。

# 檢視所有的參數
jinfo -flags <程序id>
jinfo -flags 28142

[[email protected] apache-tomcat-8.5.34]# jinfo -flags 28142
Attaching to process ID 28142, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.60-b23
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=29360128 -XX:MaxHeapSize=459276288 -XX:MaxNewSize=153092096 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=9764864 -XX:OldSize=19595264 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
Command line:  -Djava.util.logging.config.file=/export/servers/apache-tomcat-8.5.34/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -Dcatalina.base=/export/servers/apache-tomcat-8.5.34 -Dcatalina.home=/export/servers/apache-tomcat-8.5.34 -Djava.io.tmpdir=/export/servers/apache-tomcat-8.5.34/temp

# 檢視某一參數的用法
jinfo -flag <參數名> <程序id>
jinfo -flag MaxHeapSize 28142

[[email protected] apache-tomcat-8.5.34]# jinfo -flag MaxHeapSize 28142
-XX:MaxHeapSize=459276288
           

3 jvm的記憶體模型

3.1 jdk1.7堆記憶體模型

jvm優化-a第一節
  • 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中,這種情況下,一般重新啟動應用伺服器就可以已解決問題。

3.2 jdk1.8的堆記憶體模型

jvm優化-a第一節
  • 由上圖可以看出,jdk1.8的堆記憶體模型是由2部分組成的,年輕代 + 年老代
  • 年輕代 Eden + 2*Survivor
  • 年老代 OldGen
  • 在jdk1.8中變化最大的Perm區,用Metaspace(中繼資料空間) 進行了替換。
  • 需要特别說明:Metaspace所占用的記憶體空間不是在虛拟機内部,而是在本地記憶體空間,這也是與1.7的永久代最大的差別所在。
jvm優化-a第一節

3.3 為什麼要廢棄1.7中的永久區

官網:http://openjdk.java.net/jeps/122

Motivation
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加載統計
[[email protected] webapps]# jps
28582 Jps
28142 Bootstrap
[[email protected] webapps]# jstat -class 28142
Loaded  Bytes  Unloaded  Bytes     Time   
  3668  7838.3        0     0.0       1.91
           

說明:

  • Loaded:加載class的數量
  • Bytes:所占用空間大小
  • Unloaded:未加載數量
  • Bytes:未加載占用空間
  • Time時間

3.4.2 檢視編譯統計

[[email protected] webapps]# jstat -compiler 28142
Compiled Failed Invalid   Time   FailedType FailedMethod
    2505      0       0     4.15          0  
           

說明:

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

3.4.3 垃圾回收統計

[[email protected] webapps]# jstat -gc 28142
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1344.0 1344.0 301.9   0.0   11200.0   8050.4   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098

# 也可以指定列印的間隔和次數,每一秒列印一次,共列印5次
[[email protected] webapps]# jstat -gc 28142 1000 5
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
           

說明:

  • 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 檢視記憶體的使用情況

[[email protected] webapps]# jmap -heap 28142
Attaching to process ID 28142, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.60-b23

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 459276288 (438.0MB)
   NewSize                  = 9764864 (9.3125MB)
   MaxNewSize               = 153092096 (146.0MB)
   OldSize                  = 19595264 (18.6875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 12845056 (12.25MB)
   used     = 10679976 (10.185218811035156MB)
   free     = 2165080 (2.0647811889648438MB)
   83.14464335538904% used
Eden Space:
   capacity = 11468800 (10.9375MB)
   used     = 10370848 (9.890411376953125MB)
   free     = 1097952 (1.047088623046875MB)
   90.42661830357143% used
From Space:
   capacity = 1376256 (1.3125MB)
   used     = 309128 (0.29480743408203125MB)
   free     = 1067128 (1.0176925659179688MB)
   22.46151878720238% used
To Space:
   capacity = 1376256 (1.3125MB)
   used     = 0 (0.0MB)
   free     = 1376256 (1.3125MB)
   0.0% used
tenured generation:
   capacity = 28372992 (27.05859375MB)
   used     = 20720952 (19.76103973388672MB)
   free     = 7652040 (7.297554016113281MB)
   73.03054961563447% used
           

​ 14484 interned Strings occupying 1933240 bytes.

4.2 檢視記憶體中對象數量及大小

檢視所有的對象,包括活躍及非活躍的

jmap -histo <pid> | more
           

檢視活躍對象

jmap -histo:live <pid> | more

[[email protected] webapps]# jmap -histo:live 28142 | more

 num     #instances         #bytes  class name
----------------------------------------------
   1:         33674        6204920  [C
   2:          3059        1298280  [I
   3:           837         797888  [B
   4:         32047         769128  java.lang.String
   5:          4042         466176  java.lang.Class
   6:         11140         356480  java.util.HashMap$Node
   7:          3741         245952  [Ljava.lang.Object;
   8:          6805         217760  java.util.concurrent.ConcurrentHashMap$Node
   9:           863         138000  [Ljava.util.HashMap$Node;
  10:            67         101840  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  11:          6038          96608  java.lang.Object
  12:           879          73208  [Ljava.lang.String;
  13:          1294          62112  java.util.HashMap
  14:          1797          57504  java.util.Hashtable$Entry
  15:            88          49344  [Ljava.util.WeakHashMap$Entry;
  16:          1027          41080  java.util.TreeMap$Entry
  17:           842          40416  org.apache.tomcat.util.modeler.AttributeInfo
  18:           446          39248  java.lang.reflect.Method
  19:           976          39040  java.util.LinkedHashMap$Entry
  20:           516          37152  org.apache.jasper.compiler.Node$TemplateText
  21:           893          35720  java.lang.ref.SoftReference
  22:            48          34944  [S
  23:           125          34416  [[C
  24:           428          34240  java.lang.reflect.Constructor
  25:           401          28872  java.util.logging.Logger
  26:           572          22880  org.apache.jasper.compiler.Mark
  27:           438          21024  java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync
  28:           645          20640  javax.management.MBeanAttributeInfo
  29:            99          20464  [Ljava.util.Hashtable$Entry;
  30:           633          20256  java.util.concurrent.locks.ReentrantLock$NonfairSync
  31:           620          19840  java.lang.ref.WeakReference
  32:           407          19536  java.util.logging.LogManager$LoggerWeakRef
--More--
           

對象說明:

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

示例:

jmap -dump:format=b,file=/tmp/dump.dat 28142

4.4 通過jhat對dump檔案進行分析

将jvm的記憶體dump檔案中,檔案是一個二進制的檔案,不友善檢視,這時我們借助于jhat工具進行檢視。
jhat -port <port> <file>

# 示例
jhat -port 9999 /tmp/dump.dat

[[email protected] ~]# jhat -port 9999 /tmp/dump.dat
Reading from /tmp/dump.dat...
Dump file created Mon Apr 13 13:09:10 CST 2020
Snapshot read, resolving...
Resolving 148587 objects...
Chasing references, expect 29 dots.............................
Eliminating duplicate references.............................
Snapshot resolved.
Started HTTP server on port 9999
Server is ready.
           
jvm優化-a第一節

在後面有OQL查詢功能。

jvm優化-a第一節
select s from java.lang.String s where s.value.length >= 1000
           
jvm優化-a第一節

4.5 通過MAT工具對dump檔案進行分析

4.5.1 MAT工具介紹

MAT(Memory Analyzer Tool),一個基于Eclipse的分析工具,是一個快捷、功能豐富的JAVA heap分析工具,可以幫助我們查找記憶體洩漏和減少記憶體消耗。
使用記憶體分析工具從衆多的對象中進行分析,快速的計算出在記憶體中對象的占用大小,看看是誰組織了垃圾收集器的回收工作,并可以報表直覺的檢視可能造成這種結果的對象。

# 官網位址	
	https://www.eclipse.org/mat
           
jvm優化-a第一節

4.5.2 下載下傳安裝

下載下傳位址:https://www.eclipse.org/mat/downloads.php
           
jvm優化-a第一節

将下載下傳得到的MemoryAnalyzer-1.8 進行解壓。

jvm優化-a第一節

4.5.3 使用

  • 打開檔案
    jvm優化-a第一節
jvm優化-a第一節
  • 列出記憶體中的對象
jvm優化-a第一節
  • 正則比對
jvm優化-a第一節
  • 檢視對象以及它的依賴:
jvm優化-a第一節
  • 檢視肯能存在的記憶體洩漏分析
jvm優化-a第一節

5 實戰:記憶體溢出的定位和分析

記憶體溢出在實際的生産環境中會經常遇到,比如,不斷的将資料寫入到一個集合中,出現了死循環,讀取超大的檔案等等,都可能會造成記憶體溢出。

如果出現了記憶體溢出,首先我們需要定位到發生記憶體溢出的環節,并且進行分析,是正常還是非正常情況,如果是正常的需求就應該考慮加大記憶體的設定,如果是非正常的需求,那麼就要對代碼進行修改,修複這個bug.

首先,要學會如何定位問題,然後再進行分析,如何定位問題,需要借助于jmat和Mat工具進行定位分析。
           

5.1 模拟記憶體溢出

編寫代碼,向List集合中添加100萬個字元串,每個字元串由1000個UUID組成,如果程式能夠正常執行,最後列印ok.

package cn.zephyr.jvm;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class BTestJvmOutOfMemory {

    // 實作,向集合中添加100萬個字元串,每個字元串由1000個UUID組成
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            String str = "";
            for (int j = 0; j < 1000; j++) {
                str += UUID.randomUUID().toString();
            }
            list.add(str);
        }
        System.out.println("ok");
    }

}

# 設定的jvm參數: 
	-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
           
jvm優化-a第一節

5.2 運作測試

結果如下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:421)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at cn.zephyr.jvm.BTestJvmOutOfMemory.main(BTestJvmOutOfMemory.java:15)
Disconnected from the target VM, address: '127.0.0.1:57216', transport: 'socket'
           

​ Process finished with exit code 1

可以看到當記憶體溢出時,會dump檔案到java_pid25620.hprof檔案中。

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

jvm優化-a第一節

可以看到,有91.28%的記憶體由Object[]數組占有,是以比較可疑。

分析:這個可疑是正确的,因為已經有超過90%的記憶體都被他占有,這是非常可能出現記憶體溢出的。

檢視詳情:

jvm優化-a第一節

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

6 jstack的使用

有些時候我們需要檢視jvm中線程的執行情況,比如發現伺服器的CPU負載突然增高了、出現了死鎖、死循環等等,該如何分析

由于程式運作是正常的,沒有任何的輸出,從日志方面也看不出什麼問題,是以就需要看先jvm的内部線程的執行情況,然後再進行分析查找出原因。

這個時候,就需要借助于jstack指令了,jstack的作用是将正在運作的jvm的線程情況進行快照,并且列印出來。

# 用法 jstack <pid>

[[email protected] ~]# jstack 28142 
2020-04-13 23:02:07
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.60-b23 mixed mode):

"http-nio-8080-exec-20" #64 daemon prio=5 os_prio=0 tid=0x00007fc5a8124800 nid=0x6f9f waiting on condition [0x00007fc599d87000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-20" #63 daemon prio=5 os_prio=0 tid=0x00007fc5c847c800 nid=0x6f9e waiting on condition [0x00007fc599e88000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-19" #62 daemon prio=5 os_prio=0 tid=0x00007fc5b40e2000 nid=0x6f9d waiting on condition [0x00007fc59a892000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-19" #61 daemon prio=5 os_prio=0 tid=0x00007fc5ac155800 nid=0x6f9c waiting on condition [0x00007fc599a84000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-18" #60 daemon prio=5 os_prio=0 tid=0x00007fc59c0db800 nid=0x6f9a waiting on condition [0x00007fc59a690000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-18" #59 daemon prio=5 os_prio=0 tid=0x00007fc5c84a0000 nid=0x6f99 waiting on condition [0x00007fc59a08a000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-17" #58 daemon prio=5 os_prio=0 tid=0x00007fc5b000c000 nid=0x6f98 waiting on condition [0x00007fc59af99000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-17" #57 daemon prio=5 os_prio=0 tid=0x00007fc5c84ac000 nid=0x6f97 waiting on condition [0x00007fc599b85000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-16" #56 daemon prio=5 os_prio=0 tid=0x00007fc5a0037800 nid=0x6f96 waiting on condition [0x00007fc59a993000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-16" #55 daemon prio=5 os_prio=0 tid=0x00007fc5b0004800 nid=0x6f95 waiting on condition [0x00007fc599c86000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-15" #54 daemon prio=5 os_prio=0 tid=0x000000000218a000 nid=0x6f94 waiting on condition [0x00007fc59ad97000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-15" #53 daemon prio=5 os_prio=0 tid=0x00007fc5a0037000 nid=0x6f93 waiting on condition [0x00007fc59a28c000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-14" #52 daemon prio=5 os_prio=0 tid=0x00007fc5a8124000 nid=0x6f91 waiting on condition [0x00007fc59a791000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-14" #51 daemon prio=5 os_prio=0 tid=0x00007fc5b403f000 nid=0x6f90 waiting on condition [0x00007fc59a18b000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)
...
           

6.1 線程的狀态

jvm優化-a第一節

在java中線程的狀态一共被分為6種:

  • 初始态(NEW)
    • 建立一個Thread對象,但還未調用start()啟動線程時,線程處于初态
  • 運作态(RUNNABLE)在java中,運作态包括就緒态和運作态
    • 就緒态
      • 改狀态下的線程已經獲得執行所需要的資源,隻要CPU配置設定執行權就能運作。
      • 所有就緒态的線程存放在就緒隊列中
    • 運作态
      • 獲得CPU執行權,正在執行的線程
      • 由于一個CPU同一個時刻隻能執行一條線程,一次每個CPU每個時刻隻有一條運作态的線程
  • 阻塞态(BLOCKED)
    • 當一條正在執行的線程請求某一資源失敗時,就會進入阻塞态
    • 而在java中,阻塞态專指請求鎖失敗時進入的狀态
    • 由一個阻塞隊列存放所有阻塞态的線程
    • 處于阻塞态的線程會不斷的請求資源,一旦請求成功,就會進入就緒隊列,等待執行
  • 等待态
    • 當線程中調用wait、join、park函數時,目前線程就會進入等待态
    • 也有一個等待隊列存放所有等待态的線程。
    • 線程處于等待态表示等待其他線程的執行才能繼續運作
    • 進入等待态的線程會釋放CPU執行權,并釋放資源(如:鎖)
  • 逾時等待态(TIMED_WAITING)
    • 當運作中的線程調用sleep(time)、wait、join、parkNanos、parkUntil時,就會進入該狀态
    • 它和等待态一樣,并不是因為請求不到資源,二十主動進入,并且進入後需要其他線程喚醒;
    • 進入該狀态後釋放CPU執行權和占有的資源
    • 與等待态的差別:到了逾時的時間後自動進入阻塞隊列,開始競争鎖。
  • 終止态
    • 線程執行結束後的狀态。

6.2 死鎖問題

如果在生産環境發生了死鎖,我們将看到的是部署的程式沒有任何反應了,這個時候我們可以借助stack進行分析,下面實戰查下死鎖的原因。

6.2.1 構造死鎖

編寫代碼,啟動2個線程,Thread1拿到了obj1鎖,準備去拿obj2鎖時,obj2已經被Thread2鎖定,發生了死鎖。
           
public class CTestDeadLock {

    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 在linxu上運作

[[email protected] jvm]# javac CTestDeadLock.java 
[[email protected] jvm]# ll
total 20
-rw-r--r-- 1 root root  187 Apr 14 12:24 'CTestDeadLock$1.class'
-rw-r--r-- 1 root root 1083 Apr 14 12:24 'CTestDeadLock$Thread1.class'
-rw-r--r-- 1 root root 1083 Apr 14 12:24 'CTestDeadLock$Thread2.class'
-rw-r--r-- 1 root root  849 Apr 14 12:24  CTestDeadLock.class
-rw-r--r-- 1 root root 1570 Apr 14 12:24  CTestDeadLock.java
[[email protected] jvm]# ll | wc -l 
6
           
[[email protected] jvm]# java CTestDeadLock
Thread1 拿到了 obj1 的鎖!
Thread2 拿到了 obj2 的鎖!
           

6.2.3 使用jstack進行分析

[[email protected] ~]# jstack 30439
2020-04-14 12:28:01
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.60-b23 mixed mode):

"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007f9fb8001000 nid=0x771c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007f9fdc009800 nid=0x76e8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #9 prio=5 os_prio=0 tid=0x00007f9fdc0cd000 nid=0x76f2 waiting for monitor entry [0x00007f9fccefd000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at CTestDeadLock$Thread2.run(CTestDeadLock.java:47)
        - waiting to lock <0x00000000e4a81228> (a java.lang.Object)
        - locked <0x00000000e4a81238> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"Thread-0" #8 prio=5 os_prio=0 tid=0x00007f9fdc0cb000 nid=0x76f1 waiting for monitor entry [0x00007f9fccffe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at CTestDeadLock$Thread1.run(CTestDeadLock.java:27)
        - waiting to lock <0x00000000e4a81238> (a java.lang.Object)
        - locked <0x00000000e4a81228> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f9fdc0b7800 nid=0x76ef runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f9fdc0b2800 nid=0x76ee waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f9fdc0b0800 nid=0x76ed waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f9fdc0ac800 nid=0x76ec runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f9fdc075000 nid=0x76eb in Object.wait() [0x00007f9fe0966000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000e4a070b8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000000e4a070b8> (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=0x00007f9fdc073000 nid=0x76ea in Object.wait() [0x00007f9fe0a67000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000e4a06af8> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157)
        - locked <0x00000000e4a06af8> (a java.lang.ref.Reference$Lock)

"VM Thread" os_prio=0 tid=0x00007f9fdc06e000 nid=0x76e9 runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f9fdc0ba800 nid=0x76f0 waiting on condition 

JNI global references: 6


Found one Java-level deadlock:
=============================

"Thread-1":
  waiting to lock monitor 0x00007f9fc0006528 (object 0x00000000e4a81228, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f9fc0005088 (object 0x00000000e4a81238, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================

"Thread-1":
        at CTestDeadLock$Thread2.run(CTestDeadLock.java:47)
        - waiting to lock <0x00000000e4a81228> (a java.lang.Object)
        - locked <0x00000000e4a81238> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)
"Thread-0":
        at CTestDeadLock$Thread1.run(CTestDeadLock.java:27)
        - waiting to lock <0x00000000e4a81238> (a java.lang.Object)
        - locked <0x00000000e4a81228> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.
           

在輸出的資訊中,已經看到,發現了一個死鎖,關鍵看這個:

"Thread-1":
        at CTestDeadLock$Thread2.run(CTestDeadLock.java:47)
        - waiting to lock <0x00000000e4a81228> (a java.lang.Object)
        - locked <0x00000000e4a81238> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)
"Thread-0":
        at CTestDeadLock$Thread1.run(CTestDeadLock.java:27)
        - waiting to lock <0x00000000e4a81238> (a java.lang.Object)
        - locked <0x00000000e4a81228> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.
           

可以清晰的看到:

  • Thread2擷取了 <0x00000000e4a81238>,等待擷取<0x00000000e4a81228> 這個鎖
  • Thread1擷取了 <0x00000000e4a81228>,等待擷取 <0x00000000e4a81238>這個鎖7

7 VisualVM工具的使用

​ VisualVM,能夠監控線程、記憶體情況,檢視方法的CPU時間和記憶體中的對象,已被GC的對象,反向檢視配置設定的棧(如100個String對象分别由那幾個對象配置設定出來)

VisualVM使用簡單,幾乎0而配置,功能豐富,囊括了其他JDK自帶指令的所有功能。

  • 記憶體資訊
  • 線程資訊
  • Dump堆(本地程序)
  • Dump線程(本地程序)
  • 打開堆Dump,Dump可以用jmap來生成
  • 打開線程Dump
  • 生成應用快照(包含記憶體資訊,線程資訊等)
  • 性能分析,CPU分析(各個方法調用時間,檢查哪些方法耗時多),記憶體分析(各類對象占用的記憶體,檢查哪些類占用記憶體多)

7.1 啟動

在jdk的安裝目錄bin下,找到 jvisualvm.exe,輕按兩下打開即可。

jvm優化-a第一節

7.2 檢視本地程序

jvm優化-a第一節

7.3 檢視CPU,記憶體、類、線程運作資訊

jvm優化-a第一節

7.4 檢視線程詳情

jvm優化-a第一節

可以點選右上角的Dump按鈕,将線程資訊導出,就是執行jstack指令

jvm優化-a第一節

顯示的内容是一樣的。

7.5 抽樣器

抽樣器可以對CPU,記憶體在一段時間内進行好抽樣,以供分析

jvm優化-a第一節

7.6 監控遠端的JVM

VisualJVM可以監控本地程序,還可以監控遠端jvm程序,借助于JMX技術實作。

7.6.1 什麼是JMX

JMX (Java Management Extensions,java管理擴充)是一個為應用程式、裝置、系統等植入管理功能的架構.JMX可以跨越一系列異構作業系統平台,系統體系結構和網絡傳輸協定,靈活的開發無縫內建系統、網絡和服務應用管理。

7.6.2 遠端監控tomcat

要想監控遠端的tomcat,就需要在遠端的tomcat進行對JMX配置,在tomcat的bin目錄下,修改catalina.sh,添加如下的參數:
           

不需要鑒權的配置

先修改Tomcat的啟動腳本,windows下為bin/catalina.bat(linux下為catalina.sh),添加以下内容
           
  1. windows配置:
    set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false  
    set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG% 
               
  2. linux配置
    JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.authenticate=false - Dcom.sun.management.jmxremote.ssl=false"
               
  3. 配置參數的意思
  • -Dcom.sun.management.jmxremote 允許使用JMX遠端管理
  • -Dcom.sun.management.jmxremote.port=8999 JMX遠端連接配接端口
  • -Dcom.sun.management.jmxremote.authenticate=false 不進行身份認證,任何使用者都可以連接配接
  • -Dcom.sun.management.jmxremote.ssl=false 不使用ssl

需要鑒權的配置

  1. 配置
    set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access  
    set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG%
               
  2. 複制并修改授權檔案
    • JAVA_HOME/jre/lib/management下有jmxremote.access和jmxremote.password.template的模闆檔案,将兩個檔案複制到CATALINA_BASE/conf目錄下

      mv jmxremote.password.template jmxremote.password

    • vi CATALINA_BASE/conf/jmxremote.access 添加内容:

      monitorRole readonly

      controlRole readwrite

    • vi CATALINA_BASE/conf/jmxremote.password 添加内容:

      monitorRole liuke #可自定義

      controlRole liuke #可自定義

7.6.3 使用 VisualJVM連接配接遠端tomcat

添加遠端主機:

jvm優化-a第一節

在一個遠端主機上可能有多個jvm需要監控,接下來在該主機上添加需要監控的jvm

jvm優化-a第一節

連接配接成功

jvm優化-a第一節

JMX遠端連接配接端口

  • -Dcom.sun.management.jmxremote.authenticate=false 不進行身份認證,任何使用者都可以連接配接
  • -Dcom.sun.management.jmxremote.ssl=false 不使用ssl

需要鑒權的配置

  1. 配置
    set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access  
    set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG%
               
  2. 複制并修改授權檔案
    • JAVA_HOME/jre/lib/management下有jmxremote.access和jmxremote.password.template的模闆檔案,将兩個檔案複制到CATALINA_BASE/conf目錄下

      mv jmxremote.password.template jmxremote.password

    • vi CATALINA_BASE/conf/jmxremote.access 添加内容:

      monitorRole readonly

      controlRole readwrite

    • vi CATALINA_BASE/conf/jmxremote.password 添加内容:

      monitorRole liuke #可自定義

      controlRole liuke #可自定義

7.6.3 使用 VisualJVM連接配接遠端tomcat

添加遠端主機:

jvm優化-a第一節

在一個遠端主機上可能有多個jvm需要監控,接下來在該主機上添加需要監控的jvm

jvm優化-a第一節

連接配接成功

jvm優化-a第一節

使用方法和本地一樣,就可以像監控本地jvm一樣監控遠端tomcat程序