JVM:有些內(nèi)部信息我悄悄告訴你
對于 Java 的反射使用, 一般用戶都有所了解。特別是在開源框架里更是大量的使用。通過反射,我們能拿到一個Java Class 的信息。那對于 JVM 的內(nèi)部信息,像堆的使用情況、線程、甚至是創(chuàng)建對象的內(nèi)存地址、加載的類的內(nèi)容,該怎么辦呢?
其實在 JVM內(nèi),有許多內(nèi)部的信息,比如上面提到的那些,就類似于生活中的內(nèi)部消息一樣。你可以想像一些大型應(yīng)用,一些用戶的數(shù)據(jù)我們只能通過 API 授權(quán)的方式拿到,普通用戶正常使用的時候,是不可能獲取到這些內(nèi)容的。就像做為運行在 JVM 上的普通 Java應(yīng)用,也很難拿到 JVM 的信息,畢竟 JVM 更底層,是C++ 開發(fā)的。
JVM 會把這些內(nèi)部信息告訴咱們嗎?
JVM 提供了一些對外的接口,把它的內(nèi)部信息披露了出來。通過這些接口SA 才得以訪問到 JVM 內(nèi)部類的結(jié)構(gòu)和地址,也才能從底層觀察到 JVM內(nèi)部運行的細節(jié)。
你看在SA 圖形界面的HSDB內(nèi)部,長長的菜單列表,大多都是通過普通Java 應(yīng)用獲取不到的「內(nèi)部信息」。
這些都是怎么實現(xiàn)的呢?說到這兒,就不得不提 gHotSpotVMStructs。
JVM 給提供的那些接口,核心是 gHotSpotVMStructs 這個結(jié)構(gòu)。它對外暴露了JVM內(nèi)部的大量信息,像原始的堆的地址,線程、棧的地址等等。
gHotSpotVMStructs結(jié)構(gòu)指向了很多類以及這些類的字段信息。每個類都有一系列的字段,每個字段又有自己的名字,類型,是否靜態(tài)等等。如果是靜態(tài)字段這個結(jié)構(gòu)還可以用來訪問它的值。對于一個靜態(tài)的對象字段,這個結(jié)構(gòu)體還會提供目標對象的地址。通過這個根地址我們可以開始反查JVM內(nèi)部的一些組件,包括編譯器,線程還有堆。
所以要獲取和理解JVM 這些內(nèi)部信息的關(guān)鍵,是在如何解析這個gHotSpotVMStructs 結(jié)構(gòu)里面的數(shù)據(jù)。JVM不僅暴露了它的內(nèi)部類型系統(tǒng)的地址和根對象地址,還有用以解析這些數(shù)據(jù)的一些額外的符號和值。這包含類描述信息和每個字段在這個類里的偏移量,此外 JVM開發(fā)者又做了一系列的工作,手動把JVM內(nèi)部的C++類的字段映射并加載到了全局的gHotSpotVMStructs結(jié)構(gòu)里。
SA 就是解析這些信息最好的例子。通過圖形界面我們能直觀感受到解析這些信息了解到了什么,通過翻譯 gHotSpotVMStructs暴露出的這些信息,生成Java的包裝類。通過這些包裝類提供出來的接口讓訪問JVM內(nèi)部系統(tǒng)的工作變的簡單和方便,和普通的Java 應(yīng)用使用API 類似,解決了訪問和解析內(nèi)部數(shù)據(jù)的煩惱。
甚至其它的一些調(diào)試工具,診斷工具也是基于這些信息來實現(xiàn)的。
通過我們使用SA的方式,其實是通過一個「ptrace」的系統(tǒng)調(diào)用,掛起目標JVM 進程,開始讀取 gHotSpotVMStructs 這些內(nèi)存信息。
看到上面的內(nèi)容,我們大致理解了SA 的工作原理。那你如果有這樣的需求,是禁止別人通過 SA 等工具來獲取你JVM 的信息呢?
看,打哪兒指哪兒。答案就是重置gHotSpotVMStructs。這樣工具就不能解析出來這些信息了。
Stackoverflow 上有個解決方案,是編譯一個 agent,在啟動JVM 的時候掛上去,并將gHotSpotVMStructs 設(shè)置為0。
- extern void *gHotSpotVMStructs;
- int Agent_OnLoad(void *vm, char *options, void *reserved) {
- gHotSpotVMStructs = 0;
- return 0;
- }
啟動的時候,掛接到JVM上。
- java -agentpath:/path/to/libnostructs.so ...
再去執(zhí)行SA 這些工具的時候,就會拋出異常提示信息有問題
- Exception in thread "main" java.lang.reflect.InvocationTargetException
- at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
- at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
- at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
- at java.lang.reflect.Method.invoke(Method.java:498)
- at sun.tools.jstack.JStack.runJStackTool(JStack.java:140)
- at sun.tools.jstack.JStack.main(JStack.java:106)
- Caused by: java.lang.RuntimeException: gHotSpotVMStructs was not initialized properly in the remote process; can not continue
- at sun.jvm.hotspot.HotSpotTypeDataBase.readVMStructs(HotSpotTypeDataBase.java:418)
- at sun.jvm.hotspot.HotSpotTypeDataBase.<init>(HotSpotTypeDataBase.java:91)
- at sun.jvm.hotspot.HotSpotAgent.setupVM(HotSpotAgent.java:395)
- at sun.jvm.hotspot.HotSpotAgent.go(HotSpotAgent.java:305)
- at sun.jvm.hotspot.HotSpotAgent.attach(HotSpotAgent.java:140)
- at sun.jvm.hotspot.tools.Tool.start(Tool.java:185)
- at sun.jvm.hotspot.tools.Tool.execute(Tool.java:118)
- at sun.jvm.hotspot.tools.JStack.main(JStack.java:92)
- ... 6 more
本文轉(zhuǎn)載自微信公眾號「Tomcat那些事兒」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Tomcat那些事兒公眾號。