1. 程式人生 > >果斷收藏:《JVM史上最最最最全實踐優化》沒有之一?

果斷收藏:《JVM史上最最最最全實踐優化》沒有之一?

1.1 三種引數型別

jvm的引數型別分為三類,分別是 :
    標準引數 :
        -help
        -version
    -X引數(非標準引數)
        -Xint
        -Xcomp
    -XX引數(使用率較高)
        -XX:newSize
        -XX:+UseSerialGC

1.1.1 -server與-clinet引數

可以通過-server或-client設定jvm的執行引數。
    (1)它們的區別是Server VM的初始堆空間會大一些,預設使用的是並行垃圾回收器,啟動慢執行快。
    (2)Client VM相對來講會保守一些,初始堆空間會小一些,使用序列的垃圾回收器,它的目標是為了讓JVM的啟動速度更快,但執行速度會比Server VM模式慢些。
    (3)JVM在啟動的時候會根據硬體和作業系統自動選擇使用Server還是Client型別的JVM。
    (4)32位作業系統
            1)如果是Windows系統,不論硬體配置如何,都預設使用Client型別的JVM。
            2)如果是其他作業系統上,機器配置有2GB以上的記憶體同時有2個以上CPU的話預設使用server模式,否則使用client模式。
    (5)64位作業系統
            1)只有server型別,不支援client型別。

2.1 -X引數

jvm的-X引數是非標準引數,在不同版本的jvm中,引數可能會有所不同,可以通過java -X檢視非標準引數。     
    -Xmixed:混合模式執行(預設)
    -Xint:僅解釋模式執行
    -Xbootclasspath:(用;分隔的目錄和zip/jar檔案)設定搜尋路徑以引導類和資源
    -Xbootcalsspath/a:(用;分隔的目錄和zip/jar檔案)   附加在引導類路徑末尾
    -Xbootcalsspath/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:all : 顯示所有設定並繼續
    -XshowSettings:vm : 顯示所有與vm相關的設定並繼續
    -XshowSettings:properties : 顯示所有屬性設定並繼續
    -XshowSetting:locale : 顯示所有與區域設定相關的設定並繼續

2.1.1 -Xint、-Xcomp、-Xmixed

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

3.1 -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 表示新生代和老年代的比值

4.1 -Xms與-Xmx引數

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

5.1 檢視jvm的執行引數

有時候我們需要檢視jvm的執行引數,這個需求可能會存在2中情況:
第一,執行java命令時打印出執行引數;
第二,檢視正在執行的java程序的引數;

5.1.1 執行java命令時列印引數

執行java命令時列印引數,需要新增-XX:+PrintFlagsFinal引數即可。 其中引數有boolean型別和數字型別,值的操作符是=或:=,分別
代表預設值和被修改的值。
檢視所有的引數,用法 :jinfo -flags <程序id>
通過jps 或者 jps -l 檢視java程序
檢視某一引數的值,用法 :jinfo -flag <引數名> <程序id>

6.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區。

6.2 jdk1.8的堆記憶體模型

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

年輕代:Eden + 2 * Survivor

年老代:OldGen

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

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

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

移除永久代是為融合HotSpot JVM與JRockit VM而做出的努力,因為JRockit沒有永久代,不需要配置永久代。
現實使用中,由於永久代記憶體經常不夠用或發生記憶體洩漏,爆出異常java.lang.OutOfMemoryError : PermGen.
基於此,將永久區廢棄,而改元空間,改為了使用本地記憶體空間。

6.4 通過jstat命令進行檢視堆記憶體使用情況

jstat命令可以檢視堆記憶體各部分的使用量,以及載入類的數量。命令的格式如下 :
jstat[-命令選項][vmid][間隔時間/毫秒][查詢次數]

6.4.1 檢視class載入統計

說明 :

Loaded: 載入class的數量

Bytes: 所佔用空間大小

Unloaded: 未載入數量

Bytes: 未載入佔用空間

Time: 時間

6.4.2 檢視編譯統計

jstat -compiler

說明 :

Compiled: 編譯數量

Failed: 失敗數量

Invalid: 不可用數量

Time: 時間

FailedType: 失敗型別

FailedMethod: 失敗的方法

6.4.3 垃圾回收統計

jstat -gc

也可以指定列印的間隔和次數,每1秒中列印一次,共列印5次

jstat -gc 1000 5

說明 :

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:垃圾回收消耗總時間

7.1 查詢記憶體使用情況

前面通過jstat可以對jvm堆的記憶體進行統計分析,而jmap可以獲取到更加詳細的內容,如 :記憶體使用情況的彙總、對記憶體溢位的定位與分析。
檢視記憶體使用情況 :
jmap -heap <vmid>

7.2 檢視記憶體中物件數量及大小

檢視所有物件,包括活躍以及非活躍的
jmap -histo <pid> | more
檢視活躍物件
jmap -histo:live <pid> | more

物件說明 :

B : byte

C : char

D : double

F : float

I : int

J : long

Z : boolean

[ : 陣列,如[I表示int[]

[L+類名 :其他物件

7.3 將記憶體使用情況dump到檔案中

有些時候我們需要將jvm當前記憶體中的情況dump到檔案中,然後對它進行分析,jmap也是支援dump到檔案中的。
用法 :
jmap -dump:format=b,file=dumpFileName <pid>
其中b是代表二進位制
示例 :
jmap -dump:format=b,file=/tmp/dump.dat 6219

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

用法 :
jhat -port <port> <file>
例如 :
jhat -port 9999 /test/dump.dat
這個時候就可以開啟瀏覽器訪問 :127.0.0.2:9999  
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError : 設定初始堆大小和當發生記憶體溢位時,給記憶體一個快照並匯出一個dump檔案 
用MAT進行檔案分析

注:歡迎工作1到6年的Java工程師朋友們加入Java高階交流:834962734。群內提供免費的Java架構學習資料(有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化等...)這些成為架構師必備的知識體系,以及Java進階學習路線圖。

8 jstack的使用

有些時候我們需要檢視下jvm中的執行緒執行情況,比如,發現伺服器的CPU的負載突然增高了、出現了死鎖、死迴圈等,我們
如何分析呢?
由於程式是正常執行的,沒有任何的輸出,從日誌方面也看不出什麼問題,所以就需要看下jvm的內部執行緒的執行情況,然後
再進行分析查找出原因。
這個時候,就需要藉助於jstack命令了,jstack的作用是將正在執行的jvm的執行緒情況進行快照,並且打印出來 :
用法 :jstack <pid>

8.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)
            執行緒執行結束後的狀態。

8.2 分析死鎖

在執行的程式中,通過命令視窗檢視當前正在執行的執行緒id         
jps
同通過jstack進行分析 :
jstack <vmid>

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

可以清晰的看到 :
    Thread2 獲取了<0x00000000f655dc50>的鎖,等待獲取<0x00000000f655dc40>這個鎖
    Thread1 獲取了<0x00000000f655dc40>的鎖,等待獲取<0x00000000f655dc50>這個鎖
    由此可見,發生了死鎖。
可以使用VisualVM工具進行JVM問題的排查

8.3 監控遠端的jvm

VisualJVM不僅是可以監控本地jvm程序,還可以監控遠端的jvm程序,需要藉助於JMX技術實現。

8.3.1 什麼是JMX?

JMX(Java Management Extensions,即Java管理擴充套件)是一個為應用程式、裝置、系統等植入管理功能的框架,JMX可以跨越一系列操作
平臺、系統體系結構和網路傳輸協議,靈活的開發無縫整合的系統、網路和服務管理應用。

8.3.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.jmremote.ssl=false"                                                                
這幾個引數的意思是 :
#-Dcom.sun.management.jmxremote : 允許使用JMX遠端管理
#-Dcom.sun.management.jmxremote.port=9999  : JMX遠端連線埠
#-Dcom.sun.management.jmxremote.authenticate=false : 不進行身份認證,任何使用者都可以連線
#-Dcom.sun.management.jmremote.ssl=false : 不使用ssl
設定好以後儲存,並重啟tomcat
./startup.sh && tail -f ../logs/catalina.out  :  重啟tomcat並顯示啟動日誌
通過VisualVM