果斷收藏:《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