目錄:一.引言

二.基礎故障處理工具

2.1 概述

2.2. jps:虛擬機進程狀況工具

2.3. jstat:虛擬機統(tǒng)計信息監(jiān)視工具" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網站運營 > 深入理解JVM虛擬機——Java虛擬機的監(jiān)控及診斷工具大全

深入理解JVM虛擬機——Java虛擬機的監(jiān)控及診斷工具大全

時間:2023-06-28 02:21:01 | 來源:網站運營

時間:2023-06-28 02:21:01 來源:網站運營

深入理解JVM虛擬機——Java虛擬機的監(jiān)控及診斷工具大全:注意: 篇幅較長,建議收藏后再仔細閱讀!!!!!!!!!!

目錄:

一.引言

二.基礎故障處理工具

2.1 概述

2.2. jps:虛擬機進程狀況工具

2.3. jstat:虛擬機統(tǒng)計信息監(jiān)視工具

2.3. jinfo:Java配置信息工具

2.5. jmap:Java內存映像工具

2.7. jstack:Java堆棧跟蹤工具

2.8. 基礎工具總結

三. 可視化故障處理工具

3.1. JHSDB:基于服務性代理的調試工具

3.2. JConsole:Java監(jiān)視與管理控制臺

3.3. JVisualVM:多合一故障處理工具

3.4. Java Mission Control(JMC):可持續(xù)在線的監(jiān)控工具

四. HotSpot虛擬機插件及工具




一.引言

給一個系統(tǒng)定位問題的時候,知識、經驗是關鍵基礎,數(shù)據是依據,工具是運用知識處理數(shù)據的手段。

異常堆棧、虛擬機運行日志、垃圾收集器日志、線程快照(threaddump/javacore文件)、堆轉儲快照(heapdump/hprof文件)。

工具永遠都是知識技能的一層包裝,沒有什么工具是“秘密武器”,擁有了就能“包治百病”。

二.基礎故障處理工具

2.1 概述

選擇采用Java語言本身來實現(xiàn)這些故障處理工具

當應用程序部署到生產環(huán)境后,無論是人工物理接觸到服務器還是遠程Telnet到服務器上都可能會受到限制。
借助這些工具類庫里面的接口和實現(xiàn)代碼,開發(fā)者可以選擇直接在應用程序中提供功能強大的監(jiān)控分析功能。
啟用 JMX 功能

JDK5或以下版本,在程序啟動時請?zhí)砑訁?shù)“
-Dcom.sun.management.jmxremote”開啟JMX管理功能。
JDK6或以上版本,默認開啟了JMX管理。

2.2. jps:虛擬機進程狀況工具

JDK的很多小工具的名字都參考了UNIX命令的命名方式,jps(JVM Process Status Tool)是其中的典型。除了名字像UNIX的ps命令之外,它的功能也和ps命令類似可以列出正在運行的虛擬機進程,并顯示虛擬機執(zhí)行主類(Main Class,main()函數(shù)所在的類)名稱以及這些進程的本地虛擬機唯一ID(LVMID,Local Virtual Machine Identifier)。雖然功能比較單一,但它絕對是使用頻率最高的JDK 命令行工具,因為其他的JDK工具大多需要輸入它查詢到的LVMID來確定要監(jiān)控的是哪一個虛擬機進程。對于本地虛擬機進程來說,LVMID與操作系統(tǒng)的進程ID(PID,Process Identifier)是一致的,使用Windows的任務管理器或者UNIX的ps命令也可以查詢到虛擬機進程的LVMID,但如果同時啟動了多個虛擬機進程,無法根據進程名稱定位時,那就必須依賴jps命令顯示主類的功能才能區(qū)分了。

jps命令格式:

jps [ options ] [ hostid ]
jps可以通過RMI協(xié)議開啟了RMI服務的遠程虛擬機進程狀態(tài),hostid為RMI注冊表中注冊的主機名。

jps常用的option選項:




選項作用
-q只輸出LVMID
-m輸出虛擬機進程啟動時傳遞給主類main()函數(shù)的參數(shù)
-l輸出主類全名,如果進程執(zhí)行的事jar包,輸出jar路徑
-v輸出虛擬機進程啟動時JVM參數(shù)



案例

public class Jstat { /** * vm參數(shù)為 -Xms30m -Xmx30m -Xmn10m * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { Thread.sleep(1000000); }}運行之后,使用jps命令,將會展示虛擬機進程id和名字:







2.3. jinfo:Java配置信息工具

jstat是用于監(jiān)視虛擬機各種運行狀態(tài)信息的命令行工具。它可以顯示本地或者遠程虛擬機進程中的類裝載、內存、垃圾回收、JIT編譯等運行數(shù)據,在沒有GUI圖形界面,只是提供了純文本控制臺環(huán)境的服務器上,它將是運行期定位虛擬機性能問題的首選工具。
jstat的命令格式:

jstat [option vmid [interval [s|ms] [count]] ]
1. option: 參數(shù)選項
2. -t: 可以在打印的列加上Timestamp列,用于顯示系統(tǒng)運行的時間
3. -h: 可以在周期性數(shù)據數(shù)據的時候,可以在指定輸出多少行以后輸出一次表頭
4. vmid: Virtual Machine ID( 進程的 pid)
5. interval: 執(zhí)行每次的間隔時間,單位為毫秒
6. count: 用于指定輸出多少次記錄,缺省則會一直打印
7. 對于命令格式中的VMID和LVMID,如過是本地虛擬機進程,VMID和LVMID是一致的,如 果是遠程虛擬機,那VMID的格式應當是:[protocol:] [//] lvmid[@hostname[:port]/servername]
8. 參數(shù)interval 和count分別表示查詢的間隔和次數(shù),如果省略這兩個參數(shù),說明只查詢一次。
Jstat常用option選項:




選項作用
-class類裝載數(shù)量、卸載數(shù)量、總空間以及類狀態(tài)所消耗時間
-GC監(jiān)視Java堆容量狀況,包括Eden、Survivor、老年代、永久代等
-GCcapacity監(jiān)視Java堆最大、最小空間
-GCutil關注已使用空間占總空間的百分比
-GCcause類似GCutil,額外輸出上次GC的原因
-GCnew新生代GC狀況
-GCnewcapacity與-GCnew類似,輸出主要關注使用到的最大、最小空間
-GCold老年代GC狀況
-GColdcapacity與-GCold類似,輸出主要關注使用到的最大、最小空間
-GCpermcapacity輸出永久代使用到的最大、最小空間
-compiler輸出JIT編譯過的方法和耗時
-printcompilation輸出已經被JIT編譯的方法
-GCmetacapacity元數(shù)據空間統(tǒng)計



案例

加上-GC顯示將會GC堆信息,使用上一個案例,設置VM參數(shù)為 -Xms30m -Xmx30m -Xmn10m ,即初始內存30m,最大內存30m,年輕代10m。

運行程序,使用 jstat -gc 6128 命令結果如下,可以看到GC堆信息:







S0C:年輕代中第一個Survivor(幸存區(qū))的容量 (字節(jié))
S1C:年輕代中第二個Survivor(幸存區(qū))的容量 (字節(jié))
S0U :年輕代中第一個Survivor(幸存區(qū))目前已使用空間 (字節(jié))
S1U :年輕代中第二個Survivor(幸存區(qū))目前已使用空間 (字節(jié))
EC :年輕代中Eden(伊甸園)的容量 (字節(jié))
EU :年輕代中Eden(伊甸園)目前已使用空間 (字節(jié))
OC :Old代的容量 (字節(jié))
OU :Old代目前已使用空間 (字節(jié))
MC:metaspace(元空間)的容量 (字節(jié))
MU:metaspace(元空間)目前已使用空間 (字節(jié))
YGC :從應用程序啟動到采樣時年輕代中GC次數(shù)
YGCT :從應用程序啟動到采樣時年輕代中GC所用時間(s)
FGC :從應用程序啟動到采樣時old代(全GC)GC次數(shù)
FGCT :從應用程序啟動到采樣時old代(全GC)GC所用時間(s)
GCT:從應用程序啟動到采樣時GC用的總時間(s)
從圖中可以看出,各項結果符合我們的VM參數(shù)設置的信息。

2.5. jmap:Java內存映像工具

jmap命令用于生成堆轉儲快照。jmap的作用并不僅僅為了獲取dump文件,它還可以查詢finalize執(zhí)行隊列、java堆和永久代的詳細信息。如空間使用率、當前用的是哪種收集器等。
jmap格式:

jmap [option] vmid
jmap常用option選項:




選項作用
-dump生成堆轉儲快照,格式為-dump:[live,]format=b,file=,不建議使用
-finalizerinfo顯示在F-Queue中等待Finalizer線程執(zhí)行finalize方法的對象
-heap顯示java堆詳細信息,回收器種類、參數(shù)配置、分代狀況等
-histo顯示堆中對象統(tǒng)計信息,包括類、實例數(shù)量、合計容量,會先觸發(fā)GC,再統(tǒng)計信息,不建議使用
-permstat查看永久代內存狀態(tài),比較耗時,會暫停應用,不建議使用



案例:

還是上面的例子。

使用jmap -heap 6128,可以看到我們的VM參數(shù)設置的信息:







生成dump文件

jmap -dump:live,format=b,file=?C:/Users/lx/Desktop/test1.bin 9472
將生成堆轉儲快照,這里我生成到桌面。后面可以使用jhat分析dump文件。







2.7. jstack:Java堆棧跟蹤工具

jstack命令用于生成虛擬機當前時刻的線程快照(一般稱為threaddump或者javacore文件)。線程快照就是當前虛擬機內每一條線程正在執(zhí)行的方法堆棧集合,生成線程快照的主要目的是定位線程出現(xiàn)長時間停頓的原因,如線程死鎖、死循環(huán)、請求外部資源導致長時間等待等。
jstack 格式:

jstack [option] vmid
jstack常見option選項:




選項作用案例
-m如果調用本地方法,則顯示C/C++的堆棧jstack -m 1479
-l除堆棧外,顯示關于鎖的附加信息jstack -l 1479
-F當正常輸出的請求不被響應時,強制輸出線程堆棧jstack -F 1479



案例

jstack -l 9472

會輸出很多信息,我們可以找到如下信息:







可以看到,main線程正在限時等待——因為sleep的原因。

jstack 可以幫助我們用來分析線程信息,比如死鎖,狀態(tài)等。

2.8. 基礎工具總結

  1. jps將打印所有正在運行的 Java 進程。
  2. jstat允許用戶查看目標 Java 進程的類加載、即時編譯以及垃圾回收相關的信息。它常用于檢測垃圾回收問題以及內存泄漏問題。
  3. jmap允許用戶統(tǒng)計目標 Java 進程的堆中存放的 Java 對象,并將它們導出成二進制文件。
  4. jinfo將打印目標 Java 進程的配置參數(shù),并能夠改動其中 manageabe 的參數(shù)。
  5. jstack將打印目標 Java 進程中各個線程的棧軌跡、線程狀態(tài)、鎖狀況等信息。它還將自動檢測死鎖。

三. 可視化故障處理工具

3.1. JHSDB:基于服務性代理的調試工具

HSDB(Hotspot Debugger),是一款內置于 SA 中的 GUI 調試工具,可用于調試 JVM 運行時數(shù)據,從而進行故障排除。

3.1.1 HSDB發(fā)展

sa-jdi.jar

在 Java9 之前,JAVA_HOME/lib 目錄下有個 sa-jdi.jar,可以通過如下命令啟動HSDB(圖形界面)及CLHSDB(命令行)。

java -cp /Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDBsa-jdi.jar中的sa的全稱為 Serviceability Agent,它之前是sun公司提供的一個用于協(xié)助調試 HotSpot 的組件,而 HSDB 便是使用Serviceability Agent 來實現(xiàn)的。

由于Serviceability Agent 在使用的時候會先attach進程,然后暫停進程進行snapshot,最后deattach進程(進程恢復運行),所以在使用 HSDB 時要注意。

jhsdb

jhsdb 是 Java9 引入的,可以在 JAVA_HOME/bin 目錄下找到 jhsdb;它取代了 JDK9 之前的 JAVA_HOME/lib/sa-jdi.jar,可以通過下述命令來啟動 HSDB。

$ cd /Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home/bin/$ jhsdb hsdbjhsdb 有 clhsdb、debugd、hsdb、jstack、jmap、jinfo、jsnap 這些 mode 可以使用。

其中 hsdb 為 ui debugger,就是 jdk9 之前的 sun.jvm.hotspot.HSDB;而 clhsdb 即為 jdk9 之前的sun.jvm.hotspot.CLHSDB。

3.1.2 HSDB實操

3.1.2.1 啟動HSDB

檢測不同 JDK 版本需要使用不同的 HSDB 版本,否則容易出現(xiàn)無法掃描到對象等莫名其妙的問題。

Mac:JDK7 和 JDK8 均可以采用以下的方式

$ java -cp /Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB如果執(zhí)行報錯,則前面加上 sudo,或者更改 sa-jdi.jar 的權限。

sudo chmod -R 777 sa-jdi.jar本地安裝的是 JDK8,在啟動 HSDB 后,發(fā)現(xiàn)無法連接到 Java 進程,在 attach 過程中會提示如下錯誤:







網上搜索相關解決方案,建議更換 JDK 版本??梢匀⒖?Mac下安裝多個版本的JDK并隨意切換

個人在配置的過程中遇到了這樣一個問題:在切換 JDK 版本時,發(fā)現(xiàn)不生效,網上各種查找方案,動手嘗試,最后都沒有成功。解決方案:手動修改 .bash_profile 文件,增加注釋。

首次嘗試 JDK 11,但是還是無法 attach Java 進程,試了好久都不行,只能再次嘗試 JDK9.

而 JDK9 的啟動方式有些區(qū)別

$ cd /Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home/bin/$ jhsdb hsdb其中啟動版本可以使用 /usr/libexec/java_home -V 獲取 HSDB 對 Serial GC 支持的較好,因此 Debug 時增加參數(shù) -XX:+UseSerialGC。注意運行程序 Java 的版本和 hsdb 的 Java 版本要一致才行

注意:如果后續(xù)想要下載 .class 文件,啟動 hsdb 時,需要執(zhí)行 sudo jhsdb hsdb 命令。

3.1.2.2 HSDB可視化界面

比如說有這么一個 Java 程序,我們使用 Thread.sleep 方法讓其長久等待,然后獲取其進程 id。

public class InvokeTest { public static void printException(int num) { new Exception("#" + num).printStackTrace(); } public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InterruptedException { Class<?> cl = Class.forName("InvokeTest"); Method method = cl.getMethod("printException", int.class); for (int i = 1; i < 20; i++) { method.invoke(null, i); if (i == 17) { Thread.sleep(Integer.MAX_VALUE); } } }}然后在 terminal 窗口執(zhí)行 jps 命令:

27995 InvokeTest然后在 HSDB 界面點擊 file 的 attach,輸入 pid,如果按照上述步驟操作,是可以操作成功的。







attach 成功后,效果如下所示:







更多操作選擇推薦閱讀:解讀HSDB

3.1.2.3分析對象存儲區(qū)域

下面代碼中的 heatStatic、heat、heatWay 分別存儲在什么地方呢?

package com.msdn.java.hotspot.hsdb;public class Heat2 { private static Heat heatStatic = new Heat(); private Heat heat = new Heat(); public void generate() { Heat heatWay = new Heat(); System.out.println("way way"); }}class Heat{ }測試類

package com.msdn.java.hotspot.hsdb;public class HeatTest { public static void main(String[] args) { Heat2 heat2 = new Heat2(); heat2.generate(); }}關于上述問題,我們大概都知道該怎么回答:

heatStatic 屬于靜態(tài)變量,引用應該是放在方法區(qū)中,對象實例位于堆中;
heat 屬于成員變量,在堆上,作為 Heat2 對象實例的屬性字段;
heatWay 屬于局部變量,位于 Java 線程的調用棧上。
那么如何來看看這些變量在 JVM 中是怎么存儲的?這里借助 HSDB 工具來進行演示。

此處我們使用 IDEA 進行斷點調試,后續(xù)會再介紹 JDB 如何進行代碼調試。

IDEA 執(zhí)行前需要增加 JVM 參數(shù)配置,HSDB 對 Serial GC 支持的較好,因此 Debug 時增加參數(shù) -XX:+UseSerialGC;此外設置 Java Heap 為 10MB;UseCompressedOops 參數(shù)用來壓縮 64位指針,節(jié)省內存空間。關于該參數(shù)的詳細介紹,推薦閱讀本文。

最終 JVM 參數(shù)配置如下:

-XX:+UseSerialGC -Xmn10M -XX:-UseCompressedOops





然后在 Heat2 中的 System 語句處打上斷點,開始 debug 執(zhí)行上述代碼。

接著打開命令行窗口執(zhí)行 jps 命令查看我們要調試的 Java 進程的 pid 是多少:

% jps9977 HeatTest接著我們按照上文講解啟動 HSDB,注意在 IDEA 中執(zhí)行代碼時,Java 版本為 Java9,要與 HSDB 相關的 Java 版本一致。

在 attach 成功后,選中 main線程并打開其棧信息,接著打開 console 窗口,下面我將自測的命令及結果都列舉了出來,并簡要介紹其作用,以及可能遇到的問題。

首先執(zhí)行 help 命令,查看所有可用的命令

hsdb> helpAvailable commands: assert true | false attach pid | exec core buildreplayjars [ all | app | boot ] | [ prefix ] detach dis address [length] disassemble address dumpcfg { -a | id } dumpcodecache dumpideal { -a | id } dumpilt { -a | id } dumpreplaydata { <address > | -a | <thread_id> } echo [ true | false ] examine [ address/count ] | [ address,address] field [ type [ name fieldtype isStatic offset address ] ] findpc address flags [ flag | -nd ] help [ command ] history inspect expression intConstant [ name [ value ] ] jdis address jhisto jstack [-v] livenmethods longConstant [ name [ value ] ] pmap print expression printall printas type expression printmdo [ -a | expression ] printstatics [ type ] pstack [-v] quit reattach revptrs address scanoops start end [ type ] search [ heap | perm | rawheap | codecache | threads ] value source filename symbol address symboldump symboltable name thread { -a | id } threads tokenize ... type [ type [ name super isOop isInteger isUnsigned size ] ] universe verbose true | false versioncheck [ true | false ] vmstructsdump where { -a | id } hsdb> where 3587Thread 3587 Address: 0x00007fb25c00a800Java Stack Trace for mainThread state = BLOCKED - public void generate() @0x0000000116953ff8 @bci = 8, line = 15, pc = 0x0000000123cdacd7, oop = 0x000000013316f128 (Interpreted) - public static void main(java.lang.String[]) @0x00000001169539b0 @bci = 9, line = 11, pc = 0x0000000123caf4ba (Interpreted)hsdb>


3.1.2.4 主要命令簡介

命令1、universe 命令來查看GC堆的地址范圍和使用情況,可以看到我們創(chuàng)建的三個對象都是在 eden 區(qū)。因為使用的是 Java9,所以已經不存在 Perm gen 區(qū)了,

hsdb> universeHeap Parameters:Gen 0: eden [0x0000000132e00000,0x000000013318c970,0x0000000133600000) space capacity = 8388608, 44.36473846435547 used from [0x0000000133600000,0x0000000133600000,0x0000000133700000) space capacity = 1048576, 0.0 used to [0x0000000133700000,0x0000000133700000,0x0000000133800000) space capacity = 1048576, 0.0 usedInvocations: 0Gen 1: old [0x0000000133800000,0x0000000133800000,0x0000000142e00000) space capacity = 257949696, 0.0 usedInvocations: 0不借助命令的話,還可以這樣操作來查看。










命令2、scanoops 查看類型

Java 代碼里,執(zhí)行到 System 輸出語句時應該創(chuàng)建了3個 Heat 的實例,它們必然在 GC 堆里,但都在哪里,可以用scanoops命令來看:

hsdb> scanoops 0x0000000132e00000 0x000000013318c970 com.msdn.java.hotspot.hsdb.Heat0x000000013316f118 com/msdn/java/hotspot/hsdb/Heat0x000000013316f140 com/msdn/java/hotspot/hsdb/Heat0x000000013316f150 com/msdn/java/hotspot/hsdb/Heatscanoops 接受兩個必選參數(shù)和一個可選參數(shù):必選參數(shù)是要掃描的地址范圍,一個是起始地址一個是結束地址;可選參數(shù)用于指定要掃描什么類型的對象實例。實際掃描的時候會掃出指定的類型及其派生類的實例。

從 universe 命令返回結果可知,對象是在 eden 里分配的內存(注意used),所以執(zhí)行 scanoops 命令時地址范圍可以從 eden 中獲取。




命令3、findpc 命令可以進一步知道這些對象都在 eden 之中分配給 main 線程的

thread-local allocation buffer (TLAB)中

網上的多數(shù)文章都介紹 whatis 命令,不過我個人在嘗試的過程中執(zhí)行該命令報錯,如下述所示:

hsdb> whatis 0x000000012736efe8Unrecognized command. Try help...命令不行,那么換種思路,使用 HSDB 可視化窗口來查看對象的地址信息。







至于為什么無法使用 whatis 命令,原因是 Java9 的 HSDB 已經沒有 whatis 命令了,取而代之的是 findpc 命令。

hsdb> findpc 0x000000013316f118Address 0x000000013316f118: In thread-local allocation buffer for thread "main" (3587) [0x00000001331639f8,0x000000013316f160,0x000000013318c730,{ 0x000000013318c970})


命令4、inspect命令來查看對象的內容:

hsdb> inspect 0x000000013316f118instance of Oop for com/msdn/java/hotspot/hsdb/Heat @ 0x000000013316f118 @ 0x000000013316f118 (size = 16)_mark: 1_metadata._klass: InstanceKlass for com/msdn/java/hotspot/hsdb/Heat可見一個 heatStatic 實例要16字節(jié)。因為 Heat 類沒有任何 Java 層的實例字段,這里就沒有任何 Java 實例字段可顯示。

或者通過可視化工具來查看:







一個 Heat 的實例包含 2個給 VM 用的隱含字段作為對象頭,和0個Java字段。

對象頭的第一個字段是mark word,記錄該對象的GC狀態(tài)、同步狀態(tài)、identity hash code之類的多種信息。

對象頭的第二個字段是個類型信息指針,klass pointer。這里因為默認開啟了壓縮指針,所以本來應該是64位的指針存在了32位字段里。

最后還有4個字節(jié)是為了滿足對齊需求而做的填充(padding)。




命令5、mem命令來看實際內存里的數(shù)據格式

我們執(zhí)行 help 時發(fā)現(xiàn)已經沒有 mem 命令了,那么現(xiàn)在只能通過 HSDB 可視化工具來獲取信息。







關于這塊的講解可以參考 R大的文章,文章中講述還是使用 mem 命令,格式如下:mem 0x000000013316f118 2

mem 命令接受的兩個參數(shù)都必選,一個是起始地址,另一個是以字寬為單位的“長度”。

雖然我們通過 inspect 命令是知道 Heat 實例有 16 字節(jié),為什么給2暫不可知。

在實踐的過程中,發(fā)現(xiàn)了一個類似的命令:

hsdb> examine 0x000000013316f118/20x000000013316f118: 0x0000000000000001 0x0000000116954620


命令6、revptrs 反向指針

JVM 通過引用來定位堆上的具體對象,有兩種實現(xiàn)方式:句柄池和直接指針。目前 Java 默認使用的 HotSpot 虛擬機采用的便是直接指針進行對象訪問的。

我們在執(zhí)行 Java 程序時加了 UseCompressedOops 參數(shù),即使不加,Java9 也會默認開啟壓縮指針。啟用“壓縮指針”的功能把64位指針壓縮到只用32位來存。壓縮指針與非壓縮指針直接有非常簡單的1對1對應關系,前者可以看作后者的特例。關于壓縮指針,感興趣的朋友可以閱讀本文。

于是我們要找 heatStatic、heat、heatWay 這三個變量,等同于找出存有指向上述3個 Heat 實例的地址的存儲位置。

不嫌麻煩的話手工掃描內存去找也能找到,不過幸好HSDB內建了revptrs命令,可以找出“反向指針”——如果a變量引用著b對象,那么從b對象出發(fā)去找a變量就是找一個“反向指針”。

hsdb> revptrs 0x000000013316f118nullOop for java/lang/Class @ 0x000000013316d660確實找到了一個 Heat 實例的指針,在一個 java.lang.Class 的實例里。

用 findpc 命令來看看這個Class對象在哪里:

hsdb> findpc 0x000000013316d660Address 0x000000013316d660: In thread-local allocation buffer for thread "main" (3587) [0x00000001331639f8,0x000000013316f160,0x000000013318c730,{ 0x000000013318c970})可以看到這個 Class 對象也在 eden 里,具體來說在 main 線程的 TLAB 里。

這個 Class 對象是如何引用到 Heat 的實例的呢?再用 inspect 命令:

hsdb> inspect 0x000000013316d660instance of Oop for java/lang/Class @ 0x000000013316d660 @ 0x000000013316d660 (size = 184)<<Reverse pointers>>: heatStatic: Oop for com/msdn/java/hotspot/hsdb/Heat @ 0x000000013316f118 Oop for com/msdn/java/hotspot/hsdb/Heat @ 0x000000013316f118可以看到,這個 Class 對象里存著 Heat 類的靜態(tài)變量 heatStatic,指向著第一個 Heat 實例。注意該對象沒有對象頭。

靜態(tài)變量按照定義存放在方法區(qū),雖然 Java 虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆)。但現(xiàn)在在 JDK7 的 HotSpot VM 里它實質上也被放在 Java heap 里了??梢园堰@種特例看作是 HotSpot VM 把方法區(qū)的一部分數(shù)據也放在 Java heap 里了。

通過可視化工具操作也可以得到上述結果:







最終得到同樣的結果:







同理,我們查找一下第二個變量 heat 的存儲信息。

hsdb> revptrs 0x000000013316f140nullOop for com/msdn/java/hotspot/hsdb/Heat2 @ 0x000000013316f128hsdb> findpc 0x000000013316f128Address 0x000000013316f128: In thread-local allocation buffer for thread "main" (3587) [0x00000001331639f8,0x000000013316f160,0x000000013318c730,{ 0x000000013318c970})hsdb> inspect 0x000000013316f128instance of Oop for com/msdn/java/hotspot/hsdb/Heat2 @ 0x000000013316f128 @ 0x000000013316f128 (size = 24)<<Reverse pointers>>: _mark: 1_metadata._klass: InstanceKlass for com/msdn/java/hotspot/hsdb/Heat2heat: Oop for com/msdn/java/hotspot/hsdb/Heat @ 0x000000013316f140 Oop for com/msdn/java/hotspot/hsdb/Heat @ 0x000000013316f140接著來找第三個變量 heatWay:

hsdb> revptrs 0x000000013316f150nullnull回到我們的 HSDB 可視化界面,可以發(fā)現(xiàn)如下信息:







Stack Memory 窗口的內容有三欄:

仔細看會發(fā)現(xiàn)那個窗口里正好就有 0x000000013316f150 這數(shù)字,位于 0x00007000068e29e0 地址上,而這恰恰對應 main 線程上 generate()的棧楨。




3.2. JConsole:Java監(jiān)視與管理控制臺

3.2.1. jconsole簡介

JConsole(java monitoring and management console)是一款基于JMX的可視化監(jiān)視和管理工具。

3.2.2. 啟動JConsole







3.2.3. JConsole基本介紹

JConsole 基本包括以下基本功能:概述、內存、線程、類、VM概要、MBean

運行下面的程序、然后使用JConsole進行監(jiān)控;注意設置虛擬機參數(shù)

package com.jvm.jconsole;import java.util.ArrayList;import java.util.List;/** * 設置虛擬機參數(shù):-Xms100M -Xms100m -XX:+UseSerialGC -XX:+PrintGCDetails */public class Demo1 { static class OOMObject { public byte[] placeholder = new byte[64 * 1024]; } public static void fillHeap(int num) throws InterruptedException { Thread.sleep(20000); //先運行程序,在執(zhí)行監(jiān)控 List<OOMObject> list = new ArrayList<OOMObject>(); for (int i = 0; i < num; i++) { // 稍作延時,令監(jiān)視曲線的變化更加明顯 Thread.sleep(50); list.add(new OOMObject()); } System.gc(); } public static void main(String[] args) throws Exception { fillHeap(1000); while (true) { //讓其一直運行著 } }}

















3.2.4. 內存監(jiān)控

“內存”頁簽相當于可視化的jstat 命令,用于監(jiān)視受收集器管理的虛擬機內存的變換趨勢。







[GC (Allocation Failure) [DefNew: 27328K->3392K(30720K), 0.0112139 secs] 27328K->19901K(99008K), 0.0112664 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] [GC (Allocation Failure) [DefNew: 30720K->3392K(30720K), 0.0133413 secs] 47229K->40117K(99008K), 0.0133708 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [DefNew: 30664K->3374K(30720K), 0.0140975 secs] 67389K->65091K(99008K), 0.0141239 secs] [Times: user=0.00 sys=0.02, real=0.01 secs] [Full GC (System.gc()) [Tenured: 61716K->66636K(68288K), 0.0098835 secs] 66919K->66636K(99008K), [Metaspace: 9482K->9482K(1058816K)], 0.0100578 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

3.2.5. 線程監(jiān)控

如果上面的“內存”頁簽相當于可視化的jstat命令的話,“線程”頁簽的功能相當于可視化的jstack命令,遇到線程停頓時可以使用這個頁簽進行監(jiān)控分析。線程長時間停頓的主要原因主要有:等待外部資源(數(shù)據庫連接、網絡資源、設備資源等)、死循環(huán)、鎖等待(活鎖和死鎖)
下面三個方法分別等待控制臺輸入、死循環(huán)演示、線程鎖等待演示

package com.jvm.jconsole;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;public class Demo2 { public static void main(String[] args) throws IOException { waitRerouceConnection(); createBusyThread(); createLockThread(new Object()); } /** * 等待控制臺輸入 * * @throws IOException */ public static void waitRerouceConnection() throws IOException { Thread thread = new Thread(new Runnable() { @Override public void run() { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); try { br.readLine(); } catch (IOException e) { e.printStackTrace(); } } }, "waitRerouceConnection"); thread.start(); } /** * 線程死循環(huán)演示 */ public static void createBusyThread() { Thread thread = new Thread(new Runnable() { @Override public void run() { while (true) { ; } } }, "testBusyThread"); thread.start(); } /** * 線程鎖等待演示 */ public static void createLockThread(final Object lock) { Thread thread = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "testLockThread"); thread.start(); }}





waitRerouceConnection線程處于讀取數(shù)據狀態(tài),如下圖:







testBusyThread線程位于代碼45行,處于運行狀態(tài),如下圖:













只要lock對象的notify()或notifyAll()方法被調用,這個線程便可能激活以繼續(xù)執(zhí)行

3.2..6. 線程死鎖演示

第一步:運行下面代碼:

package com.jvm.jconsole;public class Demo3 { public static void main(String[] args) { User u1 = new User("u1"); User u2 = new User("u2"); Thread thread1 = new Thread(new SynAddRunalbe(u1, u2, 1, 2, true)); thread1.setName("thread1"); thread1.start(); Thread thread2 = new Thread(new SynAddRunalbe(u1, u2, 2, 1, false)); thread2.setName("thread2"); thread2.start(); } /** * 線程死鎖等待演示 */ public static class SynAddRunalbe implements Runnable { User u1, u2; int a, b; boolean flag; public SynAddRunalbe(User u1, User u2, int a, int b, boolean flag) { this.u1 = u1; this.u2 = u2; this.a = a; this.b = b; this.flag = flag; } @Override public void run() { try { if (flag) { synchronized (u1) { Thread.sleep(100); synchronized (u2) { System.out.println(a + b); } } } else { synchronized (u2) { Thread.sleep(100); synchronized (u1) { System.out.println(a + b); } } } } catch (InterruptedException e) { e.printStackTrace(); } } } public static class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public User(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '/'' + '}'; } }}thread1持有u1的鎖,thread2持有u2的鎖,thread1等待獲取u2的鎖,thread2等待獲取u1的鎖,相互需要獲取的鎖都被對方持有者,造成了死鎖。程序中出現(xiàn)了死鎖的情況,我們是比較難以發(fā)現(xiàn)的。需要依靠工具解決。剛好jconsole就是這個美妙的工具。

第二步:在jconsole中打開上面程序的監(jiān)控信息:







從上面可以看出代碼43行和50行處導致了死鎖。







關于程序死鎖的,我們還可以使用命令行工具jstack來查看java線程堆棧信息,也可以發(fā)現(xiàn)死鎖。

3.3. JVisualVM:多合一故障處理工具

3.3.1. VisualVM介紹

VisualVM 是一款免費的,集成了多個 JDK 命令行工具的可視化工具,它能為您提供強大的分析能力,對 Java 應用程序做性能分析和調優(yōu)。這些功能包括生成和分析海量數(shù)據、跟蹤內存泄漏、監(jiān)控垃圾回收器、執(zhí)行內存和 CPU 分析,同時它還支持在 MBeans 上進行瀏覽和操作。本文主要介紹如何使用 VisualVM 進行性能分析及調優(yōu)。

VisualVM位于{JAVA_HOME}/bin目錄中。

點擊運行,效果如下:







3.3.2. 查看jvm配置信息













通過jdk提供的jinfo命令工具也可以查看上面的信息。

3.3.3. 查看cpu、內存、類、線程監(jiān)控信息







3.3.4. 查看堆的變化

步驟一:運行下面的代碼

package com.jvm.visualvm;import java.util.ArrayList;import java.util.List;import java.util.UUID;import java.util.concurrent.TimeUnit;public class Demo1 { public static final int _1M = 1024 * 1024; public static void main(String[] args) throws InterruptedException { List<Object> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { list.add(new byte[100 * _1M]); TimeUnit.SECONDS.sleep(3); System.out.println(i); } }}步驟二:在VisualVM可以很清晰的看到堆內存變化信息。







3.3.5. 查看堆快照

步驟一:點擊“監(jiān)視”->”堆(dump)”可以生產堆快照信息.







步驟二:生成了以heapdump開頭的一個選項卡,內容如下:







對于“堆 dump”來說,在遠程監(jiān)控jvm的時候,VisualVM是沒有這個功能的,只有本地監(jiān)控的時候才有。

3.3.6. 導出堆快照文件

步驟一:查看堆快照,此步驟可以參考上面的“查看堆快照”功能

步驟二:右鍵點擊另存為,即可導出hprof堆快照文件,可以發(fā)給其他同事分析使用













3.3.7. 查看class對象加載信息

其次來看下永久保留區(qū)域PermGen使用情況

步驟一:運行一段類加載的程序,代碼如下:

package com.jvm.visualvm;import java.io.File;import java.lang.reflect.Method;import java.net.MalformedURLException;import java.net.URL;import java.net.URLClassLoader;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;public class Demo2 { private static List<Object> insList = new ArrayList<Object>(); public static void main(String[] args) throws Exception { permLeak(); } private static void permLeak() throws Exception { for (int i = 0; i < 2000; i++) { URL[] urls = getURLS(); URLClassLoader urlClassloader = new URLClassLoader(urls, null); Class<?> logfClass = Class.forName("org.apache.commons.logging.LogFactory", true, urlClassloader); Method getLog = logfClass.getMethod("getLog", String.class); Object result = getLog.invoke(logfClass, "TestPermGen"); insList.add(result); System.out.println(i + ": " + result); if (i % 100 == 0) { TimeUnit.SECONDS.sleep(1); } } } private static URL[] getURLS() throws MalformedURLException { File libDir = new File("D://installsoft//maven//.m2//repository3.3.9_0//commons-logging//commons-logging//1.1.1"); File[] subFiles = libDir.listFiles(); int count = subFiles.length; URL[] urls = new URL[count]; for (int i = 0; i < count; i++) { urls[i] = subFiles[i].toURI().toURL(); } return urls; }}步驟二:打開visualvm查看,metaspace







3.3.8. CPU分析:發(fā)現(xiàn)cpu使用率最高的方法

CPU 性能分析的主要目的是統(tǒng)計函數(shù)的調用情況及執(zhí)行時間,或者更簡單的情況就是統(tǒng)計應用程序的 CPU 使用情況。

沒有程序運行時的 CPU 使用情況如下圖:







步驟一:運行下列程序:

package com.jvm.visualvm;public class Demo3 { public static void main(String[] args) throws InterruptedException { cpuFix(); } /** * cpu 運行固定百分比 * * @throws InterruptedException */ public static void cpuFix() throws InterruptedException { // 80%的占有率 int busyTime = 8; // 20%的占有率 int idelTime = 2; // 開始時間 long startTime = 0; while (true) { // 開始時間 startTime = System.currentTimeMillis(); /* * 運行時間 */ while (System.currentTimeMillis() - startTime < busyTime) { ; } // 休息時間 Thread.sleep(idelTime); } }}步驟二:打開visualvm查看cpu使用情況,我的電腦是8核的,如下圖:







過高的 CPU 使用率可能是我們的程序代碼性能有問題導致的??梢郧袚Q到“抽樣器”對cpu進行采樣,可以擦看到那個方法占用的cpu最高,然后進行優(yōu)化。







從圖中可以看出cpuFix方法使用cpu最多,然后就可以進行響應的優(yōu)化了。

3.3.9. 查看線程快照:發(fā)現(xiàn)死鎖問題

Java 語言能夠很好的實現(xiàn)多線程應用程序。當我們對一個多線程應用程序進行調試或者開發(fā)后期做性能調優(yōu)的時候,往往需要了解當前程序中所有線程的運行狀態(tài),是否有死鎖、熱鎖等情況的發(fā)生,從而分析系統(tǒng)可能存在的問題。

在 VisualVM 的監(jiān)視標簽內,我們可以查看當前應用程序中所有活動線程(Live threads)和守護線程(Daemon threads)的數(shù)量等實時信息。

可以查看線程快照,發(fā)現(xiàn)系統(tǒng)的死鎖問題。

步驟一:運行下面的代碼:

package com.jvm.visualvm;public class Demo4 { public static void main(String[] args) { Obj1 obj1 = new Obj1(); Obj2 obj2 = new Obj2(); Thread thread1 = new Thread(new SynAddRunalbe(obj1, obj2, 1, 2, true)); thread1.setName("thread1"); thread1.start(); Thread thread2 = new Thread(new SynAddRunalbe(obj1, obj2, 2, 1, false)); thread2.setName("thread2"); thread2.start(); } /** * 線程死鎖等待演示 */ public static class SynAddRunalbe implements Runnable { Obj1 obj1; Obj2 obj2; int a, b; boolean flag; public SynAddRunalbe(Obj1 obj1, Obj2 obj2, int a, int b, boolean flag) { this.obj1 = obj1; this.obj2 = obj2; this.a = a; this.b = b; this.flag = flag; } @Override public void run() { try { if (flag) { synchronized (obj1) { Thread.sleep(100); synchronized (obj2) { System.out.println(a + b); } } } else { synchronized (obj2) { Thread.sleep(100); synchronized (obj1) { System.out.println(a + b); } } } } catch (InterruptedException e) { e.printStackTrace(); } } } public static class Obj1 { } public static class Obj2 { }}程序中:thread1持有obj1的鎖,thread2持有obj2的鎖,thread1等待獲取obj2的鎖,thread2等待獲取obj1的鎖,相互需要獲取的鎖都被對方持有者,造成了死鎖。程序中出現(xiàn)了死鎖的情況,我們是比較難以發(fā)現(xiàn)的。需要依靠工具解決。

步驟二:打開visualvm查看堆棧信息:







點擊dump,生成線程堆棧信息:







可以看到“Found one Java-level deadlock”,包含了導致死鎖的代碼。

"thread2": at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:50) - waiting to lock <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1) - locked <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2) at java.lang.Thread.run(Thread.java:745)"thread1": at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:43) - waiting to lock <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2) - locked <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1) at java.lang.Thread.run(Thread.java:745)上面這段信息可以看出,thread1持有Obj1對象的鎖,等待獲取Obj2的鎖,thread2持有Obj2的鎖,等待獲取Obj1的鎖,導致了死鎖。

3.3.10. 總結

本文介紹了jdk提供的一款非常強大的分析問題的一個工具VisualVM,通過他,我們可以做一下事情:

3.4. Java Mission Control(JMC):可持續(xù)在線的監(jiān)控工具

3.4.1.JMC的組成

使用 JMC可以監(jiān)視和管理 Java 應用程序,不會導致相關工具類的大幅度性能開銷,它使用為 Java 虛擬機 (JVM) 的普通自適應動態(tài)優(yōu)化收集的數(shù)據。

主要部分

Java Mission Control 插件使用 Java Management Extensions (JMX) 代理連接到 JVM
啟動JMC后,連接某個本地應用后,出現(xiàn)如下界面:







遠程連接JVM(通過JMX連接如果想要用jmc監(jiān)控遠程的JVM進程,配置方式和jvisualvm方式一一樣即可)
本地連接比較簡單這里就不在贅述,遠程連接JVM,這里利用VMWare工具進行模擬,過程中遇到一些問題,值得注意的。

首先,遠程機器被監(jiān)控的程序需要開啟調試端口,在執(zhí)行java命令行中加入以下屬性,屬性沒有以ssl安全認證方式連接的,案例中啟動監(jiān)聽端口為7091

3.4.2JMX配置(被監(jiān)控的遠程Tomcat)

進入tomcat安裝目錄安裝找到catalina.sh文件,在CATALINA_OPTS中增加一下配置:

-Dcom.sun.management.jmxremote=true -Djava.rmi.server.hostname=115.29.206.6 -Dcom.sun.management.jmxremote.port=6666 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.managementote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -XX:+UnlockCommercialFeatures -XX:+FlightRecorder配置成功之后我的CATALINA_OPTS為:

CATALINA_OPTS="-Xms1024m -Xmx6144m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Dspring.profiles.active=production -Xloggc:/data/logs/gc-`date +"%Y-%m-%d_%H%M%S"`.log -XX:MaxPermSize=1024M -Dcom.sun.management.jmxremote=true -Djava.rmi.server.hostname=115.29.206.6 -Dcom.sun.management.jmxremote.port=6666 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.managementote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -XX:+UnlockCommercialFeatures -XX:+FlightRecorder"

3.4.3主要配置項說明:







創(chuàng)建完成后雙擊MBean或者右鍵–>打開JMX控制臺,均能打開控制臺







3.4.4展示面板

3.4.4.1概覽

默認布局提供 CPU 和內存使用情況的概覽。
概覽:可以添加自定義圖表,通過概覽的加號”添加圖表”實現(xiàn);可以重置,通過”重置為默認控件”實現(xiàn)。
添加圖表后,可以通過圖表的加號添加相應的子項,并可以在圖表上右鍵詳細設置(如下圖中的Test)







3.4.4.2 MBean瀏覽器

JMC使用托管Bean (MBean) 為監(jiān)視和管理 Java 應用程序性能提供統(tǒng)一且一致的界面。MBean 是采用符合 JMX 規(guī)范的設計模式的托管對象。MBean 可以表示一個設備、一個應用程序或需要托管的任何資源。MBean 的管理界面由一組屬性、操作和通知組成。
MBean 瀏覽器提供對所有已注冊 MBean 的訪問。MBean 在管理服務器中注冊,后者可以通過與 Java Management Extensions (JMX) 兼容的客戶機訪問







要創(chuàng)建和注冊新 MBean,請單擊 MBean 面板加號圖標。執(zhí)行此操作會啟動動態(tài)創(chuàng)建和注冊新的 MBean 向導,提示為新 MBean 輸入對象名和類名。若要繼續(xù),對象名必須有效,并且類名必須是有效的 Java 類名。請注意,該向導不會驗證類是否對 MBean 服務器可用;將只進行語法檢查。要注銷特定 MBean,右鍵單擊并從上下文菜單中選擇注銷。







3.4.4.3 MBean功能

3.4.4.4 觸發(fā)器

使用觸發(fā)器選項卡可以管理滿足特定條件時觸發(fā)事件的規(guī)則。這是一種無需持續(xù)地監(jiān)視應用程序即可跟蹤運行時問題的有用方法。以灰色顯示的規(guī)則在監(jiān)視的 JVM 中不可用。默認情況下,停用所有規(guī)則。要激活某個規(guī)則,請選中該規(guī)則旁邊的復選框







規(guī)則詳細信息

創(chuàng)建觸發(fā)器







設置觸發(fā)規(guī)則的條件:







在規(guī)則觸發(fā)時發(fā)生的操作

3.4.4.5系統(tǒng)

系統(tǒng)選項卡提供了運行 JVM 的系統(tǒng)的信息、JVM 的性能屬性以及系統(tǒng)屬性列表。







要向表中添加屬性,請單擊 JVM 統(tǒng)計信息面板右上角的添加屬性按鈕。要刪除屬性,請在表中右鍵單擊該屬性,然后選擇刪除。右鍵單擊屬性后,可以更改其更新間隔、單位,而對于一些屬性,還可以設置值。

3.4.4.6內存

使用內存選項卡可以監(jiān)視應用程序使用內存資源的效率。此選項卡主要提供以下方面的信息:堆使用量、垃圾收集和活動內存池。此選項卡上提供的信息可幫助確定是否已將 JVM 配置為提供最佳應用程序性能。







在內存選項卡中,可以使用該選項卡右上角的運行完全垃圾收集按鈕手動啟動完全垃圾收集。
右鍵單擊屬性后,可以更改其更新間隔、單位,而對于一些屬性,還可以設置值。

3.4.4.7線程

使用線程選項卡可以監(jiān)視線程活動。此選項卡包含一個繪制應用程序隨時間推移的活動線程使用情況的圖形、一個由該應用程序使用的所有活動線程的表以及選定線程的堆棧跟蹤。







實時監(jiān)視最后三個值會消耗大量系統(tǒng)資源。這就是默認情況下禁用它們的原因。使用表上方相應的復選框,對這些值啟用監(jiān)視。
可以使用 Ctrl 鍵在活動線程表中選擇多個線程來顯示多個堆棧跟蹤。

3.4.4.8診斷命令

使用診斷命令可監(jiān)視 Java 應用程序的效率和性能。JMC 使用大量不同的診斷工具,包括一組可以使用診斷命令選項卡針對應用程序運行的命令。







運行 JMX 控制臺監(jiān)視 JVM 的額外成本很小,幾乎可以忽略不計。它提供低成本的應用程序監(jiān)視和概要分析 JMX標準參考

四. HotSpot虛擬機插件及工具

HotSpot虛擬機發(fā)展了二十余年,現(xiàn)在已經是一套很復雜的軟件系統(tǒng),如果深入挖掘HotSpot的源碼,可以發(fā)現(xiàn)在HotSpot的研發(fā)過程中,開發(fā)團隊曾經編寫(或者收集)過不少虛擬機的插件和輔助工 具,它們存放在HotSpot源碼hotspot/src/share/tools目錄下,包括(含曾經有過但新版本中已被移除 的):

HSDIS:JIT生成代碼反匯編

HSDIS是一個被官方推薦的HotSpot虛擬機即時編譯代碼的反匯編插件,它包含在HotSpot虛擬機 的源碼當中[2],在OpenJDK的網站[3]也可以找到單獨的源碼下載,但并沒有提供編譯后的程序。

HSDIS插件的作用是讓HotSpot的-XX:+PrintAssembly指令調用它來把即時編譯器動態(tài)生成的本地代碼還原為匯編代碼輸出,同時還會自動產生大量非常有價值的注釋,這樣我們就可以通過輸出的匯編代碼來從最本質的角度分析問題。

另外還有一點需要注意,如果使用的是SlowDebug或者FastDebug版的HotSpot,那可以直接通 過-XX:+PrintAssembly指令使用的插件;如果使用的是Product版的HotSpot,則還要額外加入一 個-XX:+UnlockDiagnosticVMOptions參數(shù)才可以工作。
測試代碼

public class Bar { int a = 1; static int b = 2; public int sum(int c) { return a + b + c; } public static void main(String[] args) { new Bar().sum(3); } } 12345678910編譯這段代碼,并使用以下命令執(zhí)行:

java -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*Bar.sum -XX:Compile-Command=compileonly,*Bar.sum test.Bar1其中,參數(shù)-Xcomp是讓虛擬機以編譯模式執(zhí)行代碼,這樣不需要執(zhí)行足夠次數(shù)來預熱就能觸發(fā)即時編譯。兩個-XX:CompileCommand的意思是讓編譯器不要內聯(lián)sum()并且只編譯sum(),-XX:+PrintAssembly就是輸出反匯編內容。
測試代碼







雖然是匯編,但代碼并不多,我們一句一句來閱讀:
1)mov%eax,-0x8000(%esp):檢查棧溢。
2)push%ebp:保存上一棧幀基址。
3)sub$0x18,%esp:給新幀分配空間。
4)mov 0x8(%ecx),%eax:取實例變量a,這里0x8(%ecx)就是ecx+0x8的意思,前面代碼片段“[Constants]”中提示了“this:ecx=‘test/Bar’”,即ecx寄存器中放的就是this對象的地址。偏移0x8是越 過this對象的對象頭,之后就是實例變量a的內存位置。這次是訪問Java堆中的數(shù)據。
5)mov$0x3d2fad8,%esi:取test.Bar在方法區(qū)的指針。
6)mov 0x68(%esi),%esi:取類變量b,這次是訪問方法區(qū)中的數(shù)據。
7)add%esi,%eax、add%edx,%eax:做2次加法,求a+b+c的值,前面的代碼把a放在eax中,把b 放在esi中,而c在[Constants]中提示了,“parm0:edx=int”,說明c在edx中。
8)add$0x18,%esp:撤銷棧幀。
9)pop%ebp:恢復上一棧幀。
10)test%eax,0x2b0100:輪詢方法返回處的SafePoint。
11)ret:方法返回。
在這個例子中測試代碼比較簡單,肉眼直接看日志中的匯編輸出是可行的,但在正式環(huán)境中-XX:+PrintAssembly的日志輸出量巨大,且難以和代碼對應起來,這就必須使用工具來輔助了。 JITWatch[5]是HSDIS經常搭配使用的可視化的編譯日志分析工具,為便于在JITWatch中讀取,讀者可使用以下參數(shù)把日志輸出到logfile文件:
-XX:+UnlockDiagnosticVMOptions
-XX:+TraceClassLoading
-XX:+LogCompilation
-XX:LogFile=/tmp/logfile.log
-XX:+PrintAssembly
-XX:+TraceClassLoading
在JITWatch中加載日志后,就可以看到執(zhí)行期間使用過的各種對象類型和對應調用過的方法了, 界面如圖4-28所示。








選擇想要查看的類和方法,即可查看對應的Java源代碼、字節(jié)碼和即時編譯器生成的匯編代碼, 如圖4-29所示。




來源



關鍵詞:虛擬,診斷,工具,理解,深入

74
73
25
news

版權所有? 億企邦 1997-2025 保留一切法律許可權利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點擊下載Chrome瀏覽器
關閉