BigData JVM運行參數-內存模型-MAT-命令調試-VisualJVM

目標

  • 我們爲什麼要學習JVM優化
  • 掌握jvm的運行參數以及參數的設置
  • 掌握jvm的內存模型(堆內存)
  • 掌握jmap命令的使用以及通過MAT工具進行分析
  • 掌握定位分析內存溢出的方法
  • 掌握jstack命令的使用
  • 掌握VisualJVM工具的使用

1.我們爲什麼學習JVM優化

在本地開發環境中我們很少會遇到對jvm進行優化的需求,一旦到了生產環境,可能將會有下面的問題:

  • 運行的應用“卡住了”,日誌不輸出,程序沒有反應
  • 服務器的CPU負載突然升高
  • 在多線程應用下,如何分配線程的數量?
  • ……

2.JVM運行參數以及參數的設置

在jvm中有很多的參數可以進行設置,這樣可以讓jvm在各種環境中都能夠高效的運行。絕大部分的參數保持默認即可。

2.1、三種參數類型

jvm的參數類型分爲三類,分別是:

  • 標準參數
    • -help
    • -version
  • -X參數 (非標準參數)
    • -Xint
    • -Xcomp
  • -XX參數(使用率較高)
    • -XX:newSize
    • -XX:+UseSerialGC

2.2、標準參數

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

用法: java [-options] class [args...]
           (執行類)
  或  java [-options] -jar jarfile [args...]
           (執行 jar 文件)


[root@k8s-master ~]# java -help

其中選項包括:
    -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@k8s-master ~]# java -version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
[root@k8s-master ~]# 


# -showversion參數是表示,先打印版本信息,再執行後面的命令,在調試時非常有用,後面會使用到。

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

public class TestJVM {

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

進行編譯、測試

[root@k8s-master test]# javac TestJVM.java 
[root@k8s-master test]# java TestJVM
雅閣先生
[root@k8s-master test]# java -Dgcx=123 TestJVM
123
[root@k8s-master test]# 

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@k8s-master test]# java -client -showversion TestJVM
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

雅閣先生

[root@k8s-master test]# java -server -showversion TestJVM
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

雅閣先生

[root@k8s-master test]# 


#由於機器是64位系統,所以不支持client模式

2.3、-X參數

jvm的-X參數是非標準參數,在不同版本的jvm中,參數可能會有所不同,可以通過java -X查看非標準參數。

[root@k8s-master 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@k8s-master test]# java -showversion -Xint TestJVM
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, interpreted mode)

雅閣先生



#強制設置爲編譯模式
[root@k8s-master test]# java -showversion -Xcomp TestJVM
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, compiled mode)

雅閣先生

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


#默認的混合模式
[root@k8s-master test]# java -showversion TestJVM
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

雅閣先生

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@k8s-master test]# java -showversion -XX:+DisableExplicitGC TestJVM
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

雅閣先生
[root@k8s-master test]# 

2.5、-Xms與-Xmx參數

-Xms與-Xmx分別是設置jvm的堆內存的初始大小和最大大小。

-Xmx2048m:等價於-XX:MaxHeapSize,設置JVM最大堆內存爲2048M。

-Xms512m:等價於-XX:InitialHeapSize,設置JVM初始堆內存爲512M。

適當的調整jvm的內存大小,可以充分利用服務器資源,讓程序跑的更快。

示例:

[root@k8s-master test]# java -Xms512m -Xms2048m TestJVM
雅閣先生
[root@k8s-master test]# 

2.6、查看jvm的運行參數

有些時候我們需要查看jvm的運行參數,這個需求可能會存在2種情況:

第一,運行java命令時打印出運行參數;

第二,查看正在運行的java進程的參數;

2.6.1、運行java命令時打印參數

運行java命令時打印參數,需要添加-XX:+PrintFlagsFinal參數即可。

[root@k8s-master test]# java -XX:+PrintFlagsFinal -version
[Global flags]
     intx ActiveProcessorCount                      = -1                                  {product}
    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 AggressiveHeap                            = 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                     = 3                                   {product}
     intx AllocatePrefetchLines                     = 3                                   {product}
     intx AllocatePrefetchStepSize                  = 64                                  {product}
     intx AllocatePrefetchStyle                     = 1                                   {product}
     bool AllowJNIEnvProxy                          = false                               {product}
     bool AllowNonVirtualCalls                      = false                               {product}
     bool AllowParallelDefineClass                  = false                               {product}
     bool AllowUserSignalHandlers                   = false                               {product}
     bool AlwaysActAsServerClassMachine             = false                               {product}
     bool AlwaysCompileLoopMethods                  = false                               {product}
     bool AlwaysLockClassLoader                     = false                               {product}
     bool AlwaysPreTouch                            = false                               {product}
     bool AlwaysRestoreFPU                          = false                               {product}
     bool AlwaysTenure                              = false                               {product}
     bool AssertOnSuspendWaitFailure                = false                               {product}
     bool AssumeMP                                  = false                               {product}
     intx AutoBoxCacheMax                           = 128                                 {C2 product}
    uintx AutoGCSelectPauseMillis                   = 5000                                {product}
     intx BCEATraceLevel                            = 0                                   {product}
     intx BackEdgeThreshold                         = 100000                              {pd product}
     bool BackgroundCompilation                     = true                                {pd product}

	......

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

示例:

[root@k8s-master test]# java -XX:+PrintFlagsFinal  -XX:+AdjustConcurrency -version
[Global flags]
     intx ActiveProcessorCount                      = -1                                  {product}
    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                        := true                                {product}
     bool AggressiveHeap                            = 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                     = 3                                   {product}
     intx AllocatePrefetchLines                     = 3                                   {product}
     intx AllocatePrefetchStepSize                  = 64                                  {product}
     intx AllocatePrefetchStyle                     = 1                                   {product}
     bool AllowJNIEnvProxy                          = false                               {product}
     bool AllowNonVirtualCalls                      = false                               {product}
     bool AllowParallelDefineClass                  = false                               {product}

2.6.2、查看正在運行的jvm參數

如果想要查看正在運行的jvm就需要藉助於jinfo命令查看。

首先,啓動一個tomcat用於測試,來觀察下運行的jvm參數。

rz (上傳到/opt/software)
tar -zxvf apache-tomcat-10.0.0-M5.tar.gz -C ../module/
cd /opt/module
cd apache-tomcat-10.0.0-M5/
bin/startup.sh

在這裏插入圖片描述

#查看所有的參數,用法:jinfo -flags <進程id>

#通過jps 或者  jps -l 查看java進程
[root@k8s-master bin]# jps 
79603 Bootstrap
81746 Jps

[root@k8s-master bin]# jps -l
81760 sun.tools.jps.Jps
79603 org.apache.catalina.startup.Bootstrap

[root@k8s-master bin]# jinfo -flags 79603
Attaching to process ID 79603, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.221-b11
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=46137344 -XX:MaxHeapSize=734003200 -XX:MaxNewSize=244318208 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=15204352 -XX:OldSize=30932992 -XX:+UseCompre
ssedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC Command line:  -Djava.util.logging.config.file=/opt/module/apache-tomcat-10.0.0-M5/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -D
java.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -Dcatalina.base=/opt/module/apache-tomcat-10.0.0-M5 -Dcatalina.home=/opt/module/apache-tomcat-10.0.0-M5 -Djava.io.tmpdir=/opt/module/apache-tomcat-10.0.0-M5/temp

#查看某一參數的值,用法:jinfo -flag <參數名> <進程id>
[root@k8s-master bin]# jinfo -flag MaxHeapSize 79603
-XX:MaxHeapSize=734003200
[root@k8s-master bin]# 

3.JVM內存模型

jvm的內存模型在1.7和1.8有較大的區別,本文是以1.8爲例進行講解,但是我們也是需要對1.7的內存模型有所瞭解,所以接下里,我們將先學習1.7再學習1.8的內存模型。

3.1、jdk1.7的堆內存模型

在這裏插入圖片描述

  • Young 年輕區(代)

    Young區被劃分爲三部分,Eden區和兩個大小嚴格相同的Survivor區,其中,Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時複製對象用,在Eden區間變滿的時候, GC就會將存活的對象移到空閒的Survivor區間中,根據JVM的策略,在經過幾次垃圾收集後,任然存活於Survivor的對象將被移動到Tenured區間。

  • Tenured 年老區

    Tenured區主要保存生命週期長的對象,一般是一些老的對象,當一些對象在Young複製轉移一定的次數以後,對象就會被轉移到Tenured區,一般如果系統中用了application級別的緩存,緩存中的對象往往會被轉移到這一區間。

  • Perm 永久區

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

  • Virtual區:

    • 最大內存和初始內存的差值,就是Virtual區。

3.2、jdk1.8的堆內存模型

在這裏插入圖片描述
由上圖可以看出,jdk1.8的內存模型是由2部分組成,年輕代 + 年老代。

年輕代:Eden + 2*Survivor

年老代:OldGen

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

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

3.3、爲什麼要廢棄1.7中的永久區?

官網給出瞭解釋:http://openjdk.java.net/jeps/122
查看 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加載統計

[root@k8s-master bin]# jps
85568 Jps
79603 Bootstrap
[root@k8s-master bin]# jstat -class 79603
Loaded  Bytes  Unloaded  Bytes     Time   
  3681  8060.6        0     0.0       2.83
[root@k8s-master bin]# 

說明:

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

3.4.2、查看編譯統計

[root@k8s-master bin]# jstat -compiler 79603
Compiled Failed Invalid   Time   FailedType FailedMethod
    2791      0       0     4.48          0             
[root@k8s-master bin]# 

說明:

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

3.4.3、垃圾回收統計

#指定打印的間隔和次數,每1秒中打印一次,共打印5[root@k8s-master bin]# jstat -gc 79603 1000 10
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
13824.0 10240.0  0.0   10221.9 90112.0   3249.8   37376.0    22556.0   27008.0 26302.3 2944.0 2676.4      7    0.535   1      0.152    0.687
[root@k8s-master bin]# 

說明:

  • 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@k8s-master bin]# jmap -heap 79603
Attaching to process ID 79603, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.221-b11

using thread-local object allocation.
Parallel GC with 2 thread(s)

Heap Configuration: #堆內存使用配置
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 734003200 (700.0MB)
   NewSize                  = 15204352 (14.5MB)
   MaxNewSize               = 244318208 (233.0MB)
   OldSize                  = 30932992 (29.5MB)
   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 = 92274688 (88.0MB)
   used     = 4100112 (3.9101715087890625MB)
   free     = 88174576 (84.08982849121094MB)
   4.443376714533025% used
From Space:
   capacity = 10485760 (10.0MB)
   used     = 10467224 (9.982322692871094MB)
   free     = 18536 (0.01767730712890625MB)
   99.82322692871094% used
To Space:
   capacity = 14155776 (13.5MB)
   used     = 0 (0.0MB)
   free     = 14155776 (13.5MB)
   0.0% used
PS Old Generation #老年代
   capacity = 38273024 (36.5MB)
   used     = 23097336 (22.02733612060547MB)
   free     = 15175688 (14.472663879394531MB)
   60.3488660838506% used

16904 interned Strings occupying 1499664 bytes.
[root@k8s-master bin]# 

4.2 查看內存中對象數量及大小

#查看所有對象,包括活躍以及非活躍的
jmap -histo <pid> | more

#查看活躍對象
jmap -histo:live <pid> | more

[root@k8s-master bin]# jmap -histo:live 79603 | more

 num     #instances         #bytes  class name
----------------------------------------------
   1:         37783        3647072  [C
   2:         36664         879936  java.lang.String
   3:          1483         676424  [B
   4:         16507         528224  java.util.HashMap$Node
   5:          4150         477928  java.lang.Class
   6:          4127         363176  java.lang.reflect.Method
   7:          9798         313536  java.util.concurrent.ConcurrentHashMap$Node
   8:          2333         279168  [I
   9:          4331         260720  [Ljava.lang.Object;
  10:           883         181288  [Ljava.util.HashMap$Node;
  11:            97         132216  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  12:          6622         105952  java.lang.Object
  13:          2296          73472  java.util.Hashtable$Entry
  14:          1482          71136  java.util.HashMap
  15:           907          57880  [Ljava.lang.String;
  16:            92          50176  [Ljava.util.WeakHashMap$Entry;
  17:          2275          48560  [Ljava.lang.Class;
  18:          1097          43880  java.util.LinkedHashMap$Entry
  19:           884          42432  org.apache.tomcat.util.modeler.AttributeInfo
  20:             9          37008  [Ljava.nio.ByteBuffer;
  21:            40          35856  [S
  22:           846          33840  java.lang.ref.SoftReference
  23:          1036          33152  java.lang.ref.WeakReference
  24:           620          29760  java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync
  25:          1238          29712  java.util.ArrayList
  26:           138          28288  [Ljava.util.Hashtable$Entry;
  27:           389          28008  java.util.logging.Logger
  28:          1001          24024  java.util.LinkedList$Node
  29:           677          21664  java.util.concurrent.locks.ReentrantLock$NonfairSync
  30:           613          19616  javax.management.MBeanAttributeInfo
  31:           395          18960  java.util.logging.LogManager$LoggerWeakRef
  32:           431          17240  java.math.BigInteger
  33:           344          16512  org.apache.tomcat.util.modeler.OperationInfo
  34:           668          16032  java.util.concurrent.CopyOnWriteArrayList
  35:           178          15664  org.apache.catalina.webresources.CachedResource
  36:           621          14904  java.util.concurrent.locks.ReentrantReadWriteLock
  37:           357          14280  jakarta.servlet.jsp.tagext.TagAttributeInfo
  38:           168          13440  java.lang.reflect.Constructor

  。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
  
#對象說明
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 79603
[root@k8s-master bin]# clear
[root@k8s-master bin]# jmap -dump:format=b,file=/tmp/dump.dat 79603
Dumping heap to /tmp/dump.dat ...
Heap dump file created
[root@k8s-master bin]# cd /tmp/
[root@k8s-master tmp]# ll
總用量 18516
-rw------- 1 root root 18959668 528 11:12 dump.dat
drwxr-xr-x 2 root root       19 528 11:12 hsperfdata_root
drwx------ 3 root root       17 528 08:25 systemd-private-ca665d725bd54fc3aca13b61071b9d4d-chronyd.service-0ha2Yj
drwx------ 2 root root        6 527 09:30 vmware-root_7268-2856323754
drwx------ 2 root root        6 525 10:18 vmware-root_7278-2831355174
drwx------ 2 root root        6 519 09:04 vmware-root_7382-3099672000
drwx------ 2 root root        6 527 09:07 vmware-root_7411-4156401571
drwx------ 2 root root        6 528 08:25 vmware-root_7447-4155942846
drwx------ 2 root root        6 527 16:28 vmware-root_7462-3124836936
drwx------ 2 root root        6 525 09:54 vmware-root_7469-4147488282
drwx------ 2 root root        6 520 10:12 vmware-root_7570-2831289637
[root@k8s-master tmp]# 

4.4、通過jhat對dump文件進行分析

在上一小節中,我們將jvm的內存dump到文件中,這個文件是一個二進制的文件,不方便查看,這時我們可以藉助於jhat工具進行查看。

#用法:
jhat -port <port> <file>

#示例:
[root@k8s-master tmp]# jhat -port 9999 dump.dat 
Reading from dump.dat...
Dump file created Thu May 28 11:12:17 CST 2020
Snapshot read, resolving...
Resolving 181134 objects...
Chasing references, expect 36 dots....................................
Eliminating duplicate references....................................
Snapshot resolved.
Started HTTP server on port 9999
Server is ready.

訪問地址:http://ip:9999 拉倒滾動條最下
在這裏插入圖片描述

OQL查詢功能
在這裏插入圖片描述

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

4.5.1、MAT工具介紹

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

官網地址:https://www.eclipse.org/mat/

4.5.2、下載安裝

下載地址:https://www.eclipse.org/mat/downloads.php
在這裏插入圖片描述
下載後解壓zip文件
在這裏插入圖片描述

4.5.3、使用

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
查看可能存在內存泄漏的分析
在這裏插入圖片描述

5、實戰:內存溢出的定位與分析

內存溢出在實際的生產環境中經常會遇到,比如,不斷的將數據寫入到一個集合中,出現了死循環,讀取超大的文件等等,都可能會造成內存溢出。

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

首先,我們得先學會如何定位問題,然後再進行分析。如何定位問題呢,我們需要藉助於jmap與MAT工具進行定位分析。

接下來,我們模擬內存溢出的場景。

5.1、模擬內存溢出

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

package com.gcxzflgl.springdemo;

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");
    }
}

運行結果已經內存異常打印出堆棧信息文件

D:\jdk\jdk\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:63584,suspend=y,server=n -Dvisualvm.id=12663888379118 -Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError -javaagent:C:\Users\admin\.IntelliJIdea2018.2\system\captureAgent\debugger-agent.jar=file:/C:/Users/admin/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath com.gcxzflgl.springdemo.TestJvmOutOfMemory
Connected to the target VM, address: '127.0.0.1:63584', transport: 'socket'
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid11628.hprof ...
Exception in thread "main" Heap dump file created [8152677 bytes in 0.025 secs]
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 com.gcxzflgl.springdemo.TestJvmOutOfMemory.main(TestJvmOutOfMemory.java:14)
Disconnected from the target VM, address: '127.0.0.1:63584', transport: 'socket'

Process finished with exit code 1

配置運行參數

#參數如下:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

在這裏插入圖片描述
將生成的文件通過MAT工具分析
在這裏插入圖片描述

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

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

查看詳情:
在這裏插入圖片描述
可以看到集合中存儲了大量的uuid字符串。

6、jstack的使用

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

由於程序是正常運行的,沒有任何的輸出,從日誌方面也看不出什麼問題,所以就需要看下jvm的內部線程的執行情況,然後再進行分析查找出原因。

這個時候,就需要藉助於jstack命令了,jstack的作用是將正在運行的jvm的線程情況進行快照,並且打印出來:

#用法:jstack <pid>

6.1、線程的狀態

在這裏插入圖片描述

在Java中線程的狀態一共被分成6種:

  • 初始態(NEW)

    • 創建一個Thread對象,但還未調用start()啓動線程時,線程處於初始態。
  • 運行態(RUNNABLE),在Java中,運行態包括 就緒態 和 運行態。

    • 就緒態

      • 該狀態下的線程已經獲得執行所需的所有資源,只要CPU分配執行權就能運行。
      • 所有就緒態的線程存放在就緒隊列中。
    • 運行態

      • 獲得CPU執行權,正在執行的線程。
      • 由於一個CPU同一時刻只能執行一條線程,因此每個CPU每個時刻只有一條運行態的線程。
  • 阻塞態(BLOCKED)

    • 當一條正在執行的線程請求某一資源失敗時,就會進入阻塞態。
    • 而在Java中,阻塞態專指請求鎖失敗時進入的狀態。
    • 由一個阻塞隊列存放所有阻塞態的線程。
    • 處於阻塞態的線程會不斷請求資源,一旦請求成功,就會進入就緒隊列,等待執行。
  • 等待態(WAITING)

    • 當前線程中調用wait、join、park函數時,當前線程就會進入等待態。
    • 也有一個等待隊列存放所有等待態的線程。
    • 線程處於等待態表示它需要等待其他線程的指示才能繼續運行。
    • 進入等待態的線程會釋放CPU執行權,並釋放資源(如:鎖)
  • 超時等待態(TIMED_WAITING)

    • 當運行中的線程調用sleep(time)、wait、join、parkNanos、parkUntil時,就會進入該狀態;
    • 它和等待態一樣,並不是因爲請求不到資源,而是主動進入,並且進入後需要其他線程喚醒;
    • 進入該狀態後釋放CPU執行權 和 佔有的資源。
    • 與等待態的區別:到了超時時間後自動進入阻塞隊列,開始競爭鎖。
  • 終止態(TERMINATED)

    • 線程執行結束後的狀態。

6.2、實戰:死鎖問題

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

6.2.1、構造死鎖

編寫代碼,啓動2個線程,Thread1拿到了obj1鎖,準備去拿obj2鎖時,obj2已經被Thread2鎖定,所以發送了死鎖。

package com.gcxzflgl.springdemo;

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 的鎖!");
                }
            }
        }
    }

}

[root@k8s-master test]# javac TestDeadLock.java 
[root@k8s-master test]# java TestDeadLock
Thread1 拿到了 obj1 的鎖!
Thread2 拿到了 obj2 的鎖!

#這裏發生了死鎖,程序一直將等待下去
[root@k8s-master ~]# jstack 102920

Java stack information for the threads listed above:
===================================================
"Thread-1":
	at TestDeadLock$Thread2.run(TestDeadLock.java:49)
	- waiting to lock <0x00000000f175aa58> (a java.lang.Object)
	- locked <0x00000000f175aa68> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)
"Thread-0":
	at TestDeadLock$Thread1.run(TestDeadLock.java:29)
	- waiting to lock <0x00000000f175aa68> (a java.lang.Object)
	- locked <0x00000000f175aa58> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

可以清晰的看到:

  • Thread2獲取了 <0x00000000f175aa58> 的鎖,等待獲取 <0x00000000f175aa68> 這個鎖
  • Thread1獲取了 <0x00000000f175aa68> 的鎖,等待獲取 <0x00000000f175aa58> 這個鎖
  • 由此可見,發生了死鎖。

7、VisualVM工具的使用

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

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

  • 內存信息
  • 線程信息
  • Dump堆(本地進程)
  • Dump線程(本地進程)
  • 打開堆Dump。堆Dump可以用jmap來生成。
  • 打開線程Dump
  • 生成應用快照(包含內存信息、線程信息等等)
  • 性能分析。CPU分析(各個方法調用時間,檢查哪些方法耗時多),內存分析(各類對象佔用的內存,檢查哪些類佔用內存多)
  • ……

我們用idea查看
首先檢查idea是否安裝 VisualVM Launcher插件
在這裏插入圖片描述
在這裏插入圖片描述
設置好後選擇圖中按鈕啓動
在這裏插入圖片描述
啓動後看到這個界面,進行實時監控
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章