天天看點

JVM,JVMIT, Hotspot,OpenJDK, Dynamic Attach Mechanism, Serviceability Agent 概述

JVM是一個虛拟機概念,即一個模拟真實機器的軟體機器。像真正的機器一樣,它有一個指令集,一個虛拟的計算機架構和一個執行模型。它能夠運作用這個虛拟指令集編寫的代碼,就像真正的機器能夠運作機器代碼一樣。

HotSpot的正式釋出名稱為"Java HotSpot Performance Engine",是JVM的一個實作,包含了伺服器版和桌面應用程式版,現時由Oracle維護并釋出。它利用JIT及自适應優化技術(自動查找性能熱點并進行動态優化,這也是HotSpot名字的由來)來提高性能。

OpenJDK是一個開源實作HotSpot(以及JDK的許多其他部分,如編譯器、api、工具等)的項目。

這是一個 Sun 擴充,允許工具“附加”到另一個運作 Java 代碼的程序并在該程序中啟動 JVM TI 代理或 java.lang.instrument 代理。這也允許從目标 JVM 擷取系統屬性。

此 API 的 Sun 實作還包括一些 HotSpot 特定方法,這些方法允許從 HotSpot 擷取附加資訊:

本地 JVM 的 ctrl-break 輸出

來自遠端 JVM 的 ctrl-break 輸出

堆的轉儲(dump)

顯示在目标 JVM 中加載的類的執行個體數的直方圖。可以計算所有執行個體或僅計算“活動”執行個體。

可管理的指令行标志的值。也可以設定此類标志。

動态附加在目标 JVM 中有一個附加偵聽器線程。這是在第一個附加請求發生時啟動的線程。在 Linux 和 Solaris 上,用戶端建立一個名為 .attach_pid(pid) 的檔案并向目标 JVM 程序發送一個 SIGQUIT。此檔案的存在會導緻 HotSpot 中的 SIGQUIT 的handler去啟動附加偵聽器線程。在 Windows 上,用戶端使用 Win32 CreateRemoteThread 函數在目标程序中建立一個新線程。

然後附加偵聽器線程以依賴于作業系統的方式與源 JVM 通信:

在 Solaris 上,使用 Doors IPC 機制。Doors附加到檔案系統中的一個檔案,以便客戶可以通路它。

在 Linux 上,使用 Unix 域套接字。這個套接字綁定到檔案系統中的一個檔案,以便用戶端可以通路它。

在 Windows 上,建立的線程獲得由用戶端提供服務的管道(pipe)名稱。操作的結果由目标 JVM 寫入此管道。

Dynamic Attach機制提供了連接配接到運作中的VM和執行幾個預定義指令之一的方法。您可以要求JVM轉儲Java堆、列印堆棧跟蹤、更改某些VM标志、加載代理庫,等等。VM自己執行指令,是以為了響應,它必須處于活動狀态和正常狀态。

動态附加的Java API可以在相同的tools.jar檔案中找到。請注意,這是一個特定于供應商的API,隻适用于OpenJDK和Oracle的JDK。

連接配接到一個正在運作的Java程序是很簡單的;您隻需知道目标程序ID(pid),如下面的代碼所示。(動态綁定不需要特殊的虛拟機選項。它可以連接配接到任何本地HotSpot JVM,除非它以-XX:+DisableAttachMechanism标志啟動。)

下面展示如何将Java agent注入到運作中的JVM中。Java agent是用于檢測應用程式的一種程式。它應該被打包到一個JAR檔案中,并包含一個具有agentmain方法的類。

Instrumentation API使Java agent能夠轉換現有類的位元組碼。與Dynamic Attach一起使用時,可以實作修改正在運作的程式的代碼,即使應用程式在沒有任何調試工具的情況下啟動。(Elkeid就是使用Jattach,通過這樣的方法實作的)

下面是一個安裝新版本MyClass的簡單代理:

redefineClasses API的限制:類檔案的新版本不能添加新方法或字段,也不能删除現有成員。基本上,隻有方法體可以更改,但這對于熱修複來說已經足夠了。

動态附加機制需要JVM的充分合作。如果JVM挂起或too busy,那麼它就沒有用了。當這種情況發生時,就應該調用蠻力,例如可服務性代理(serviceability agent)。

HotSpot Serviceability Agent (SA)從虛拟機的角度提供了Java程序的底層視角。它了解有關Java HotSpot VM内部結構的一切,包括堆布局、系統字典、編譯代碼、線程和堆棧。此外,這些資訊可以通過清晰簡單的Java API獲得,是以開發人員無需使用反彙程式設計式和其他黑客工具就可以從中受益。

SA最初是由Java HotSpot VM工程師發明的,用于調試JDK内部的崩潰。然而,他們後來意識到它可能對更廣泛的開發團隊有幫助,現在它與正常的JDK捆綁在一起。我們可以通過在類路徑中包含<code>{JAVA_HOME} /lib/sa-jdi.jar</code>開始使用SA,但是,該API不是标準的,并且可能會在将來的任何JDK版本中更改。

Custom tools通常是擴充現有的Tool類,該類已經能夠解析參數并附加到運作的VM上。是以我們隻需要在重寫的run方法中實作自定義邏輯

<code>VM.getvm()</code>是通路Java HotSpot VM内部結構的起點。下面的例子是使用SystemDictionary周遊所有裝載的類及其類加載器。在檢測與類加載相關的記憶體洩漏時,類似的技術可能很有用。

以上都很簡單。SA的真正功能是恢複VM結構,無論是從運作中Java程序的記憶體,還是在作業系統配置為建立此類轉儲時從異常終止的程序的核心轉儲恢複VM結構。SA提供類似反射的API來檢查Java對象并提取所需的字段。與從同一程序中工作的反射不同,SA讀取不同程序的記憶體或解析核心轉儲檔案。基于這一特性的工具可以實作一些特别的技術,比如從運作中的web伺服器上竊取私鑰。下面的代碼實作了掃描目标程序的堆,尋找java.security.PrivateKey的執行個體并列印它們的内容。

SA不需要來自目标JVM的合作,并且目前沒有辦法防止SA互動。 不過,這不是擔心的理由。 SA通常需要root特權來附加到正在運作的程序。 另外,請記住,在附加SA時,目标JVM保持挂起狀态。

JVM工具接口(JVM TI)是一個标準的程式設計接口,專門為調試、監視和概要分析打算在JVM上運作的軟體而設計。 JVM TI最好的一點是它的公共規範,它不綁定任何特定的實作。 它并不要求每個JVM都提供所有的JVM TI功能; 然而,大多數流行的jvm都提供了。

該接口通過С語言頭檔案jvmti.h公開。 基于JVM ti的工具,稱為代理(agents),通常是用C或C++編寫的。 它們運作在同一個程序中,并通過調用JVM TI函數直接與JVM通信。 這個接口有點類似于Java本地接口(Java Native interface, JNI)。

代理可以在JVM啟動時啟動(在-agentlib或-agentpath JVM參數中指定),也可以在運作時使用Dynamic Attach機制加載。 為了支援這些選項,agent應該定義一個或多個入口點:

Agent_OnLoad, which is called automatically by the JVM early at startup time

Agent_OnAttach, which is called whenever the library is loaded at runtime

代理通常做的第一件事是擷取對JVM TI環境(jvmtiEnv)的引用,這是調用JVM TI函數所必需的。

JVM TI為調試器通常做的所有事情提供了功能。 您可以管理線程、周遊線程的堆棧、疊代Java堆、查詢局部變量、設定斷點、操縱Java類、攔截本機方法,以及執行許多其他操作。 除此之外,代理還可以訂閱事件通知:當事件發生時,JVM将調用提供的回調函數。

對JVM TI功能的通路是基于能力的; 也就是說,代理必須顯式地請求它将要使用的功能。 大多數功能在運作時可用,但有些功能隻能在OnLoad階段(即沒有加載類和沒有執行位元組碼的階段)請求。 例如,<code>can_access_local_variables</code>功能僅在啟動時可用,因為JVM需要提前禁用某些優化,以便保留關于所有本地變量的資訊。

下面的示例請求一種生成異常事件的能力,并設定回調來接收關于所有抛出的Java異常(包括已捕獲的和未捕獲的)的通知。

回調函數接收關于異常的所有詳細資訊:線程、方法和所抛出異常的位元組碼索引。 回調還有一個對JNI環境的引用。 這意味着您可以從内部調用任何JNI函數。 例如,您可以使用JNI調用Throwable.printStackTrace()。 是以,代理将在捕獲異常之前列印所有異常,包括被忽略的異常。

您可以使用JVM TI做更多有用的事情。 除了異常之外,還可以跟蹤類加載、垃圾收集、鎖争用、線程活動等。

JVM TI經常與Java調試器代理混淆。 有一種流行的誤解認為JVM TI會損害安全性并降低Java應用程式的性能。 然而,Java調試連線協定(JDWP)代理隻是基于JVM ti的工具的一個例子; 技術本身并不意味着安全或性能的後果。 應用程式是否會受到代理開銷的影響,完全取決于代理做什麼以及它請求哪些功能。 可以将JVM TI視為JNI的一種擴充。 這項技術絕對值得一試。

creating-your-own-debugging-tools