JPDA(Java Platform Debugger Architecture)是 Java 平台調試體系結構的縮寫,通過 JPDA 提供的 API,開發人員可以方便靈活的搭建 Java 調試應用程序。 JPDA 主要由三個部分組成:Java 虛擬機工具接口(JVMTI),Java 調試線協議(JDWP),以及 Java 調試接口(JDI),本系列將會詳細介紹這三個模塊的內部細節、通過實例為讀者揭開 JPDA 的面紗。本文是該系列的第一篇,將會著重從整體上介紹 JPDA 的各個組成,闡述它們彼此之間的內在關聯。
JPDA 概述
所有的程序員都會遇到 bug,對於運行態的錯誤,我們往往需要一些方法來觀察和測試運行態中的環境。在 Java 程序中,最簡單的,您是否嘗試過使用 System.out.println() 來輸出您的 Java 程序的執行中的各種變量狀態來發現您的 Java 程序運行時的問題?這種方式方便易用,在一些簡單的情況下能夠解決您的問題,但是如果當您的程序運行在遠程環境上,或者當前環境不允許控制台終端輸出(比如,考慮一下虛擬機初始化之時),您無法獲取終端輸出的時候呢?或者,如果您根本無法本地修改運行您的程序?
無須擔心,您可以通過很多的調試工具來幫助您解決這個問題,常見的 IDE 都附帶一個非常直觀簡單的調試工具,比如 Eclipse(圖 1)就提供一個功能非常全面,操作非常簡單的調試器。
圖 1. 使用 Eclipse 調試 Java 程序
其他的一些常見的 Java IDE,比如 Netbeans 和 IntelliJ 等等也都提供了類似的功能,您甚至能不用 IDE 提供的圖形界面,使用 JDK 自帶的 jdb 工具,以文本命令的形式來調試您的 Java 程序。這些形形色色的調試器都支持本地和遠程的程序調試,那麼它們是如何被開發的?它們之間存在著什麼樣的聯系呢?我們不得不提及 Java 的調試體系—— JPDA 。
我們知道,Java 程序都是運行在 Java 虛擬機上的,我們要調試 Java 程序,事實上就需要向 Java 虛擬機請求當前運行態的狀態,並對虛擬機發出一定的指令,設置一些回調等等,那麼 Java 的調試體系,就是虛擬機的一整套用於調試的工具和接口。
對於 Java 虛擬機接口熟悉的人來說,您一定還記得 Java 提供了兩個接口體系,JVMPI(Java Virtual Machine Profiler Interface)和 JVMDI(Java Virtual Machine Debug Interface),而它們,以及在 Java SE 5 中准備代替它們的 JVMTI(Java Virtual Machine Tool Interface),都是 Java 平台調試體系(Java Platform Debugger Architecture,JPDA)的重要組成部分。 Java SE 自 1.2.2 版就開始推出 Java 平台調試體系結構(JPDA)工具集,而從 JDK 1.3.x 開始,Java SDK 就提供了對 Java 平台調試體系結構的直接支持。顧名思義,這個體系為開發人員提供了一整套用於調試 Java 程序的 API,是一套用於開發 Java 調試工具的接口和協議。本質上說,它是我們通向虛擬機,考察虛擬機運行態的一個通道,一套工具。理解這一點對於學習 JPDA 非常重要。
換句話說,通過 JPDA 這套接口,我們就可以開發自己的調試工具。通過這些 JPDA 提供的接口和協議,調試器開發人員就能根據特定開發者的需求,擴展定制 Java 調試應用程序,開發出吸引開發人員使用的調試工具。前面我們提到的 IDE 調試工具都是基於 JPDA 體系開發的,區別僅僅在於它們可能提供了不同的圖形界面、具有一些不同的自定義功能。另外,我們要注意的是,JPDA 是一套標准,任何的 JDK 實現都必須完成這個標准,因此,通過 JPDA 開發出來的調試工具先天具有跨平台、不依賴虛擬機實現、JDK 版本無關等移植優點,因此大部分的調試工具都是基於這個體系的。
JPDA 組成模塊
JPDA 定義了一個完整獨立的體系,它由三個相對獨立的層次共同組成,而且規定了它們三者之間的交互方式,或者說定義了它們通信的接口。這三個層次由低到高分別是 Java 虛擬機工具接口(JVMTI),Java 調試線協議(JDWP)以及 Java 調試接口(JDI)。這三個模塊把調試過程分解成幾個很自然的概念:調試者(debugger)和被調試者(debuggee),以及他們中間的通信器。被調試者運行於我們想調試的 Java 虛擬機之上,它可以通過 JVMTI 這個標准接口,監控當前虛擬機的信息;調試者定義了用戶可使用的調試接口,通過這些接口,用戶可以對被調試虛擬機發送調試命令,同時調試者接受並顯示調試結果。在調試者和被調試著之間,調試命令和調試結果,都是通過 JDWP 的通訊協議傳輸的。所有的命令被封裝成 JDWP 命令包,通過傳輸層發送給被調試者,被調試者接收到 JDWP 命令包後,解析這個命令並轉化為 JVMTI 的調用,在被調試者上運行。類似的,JVMTI 的運行結果,被格式化成 JDWP 數據包,發送給調試者並返回給 JDI 調用。而調試器開發人員就是通過 JDI 得到數據,發出指令。圖 2 展示了這個過程:
圖 2. JPDA 模塊層次
當然,開發人員完全可以不使用完整的三個層次,而是基於其中的某一個層次開發自己的應用。比如您完全可以僅僅依靠通過 JVMTI 函數開發一個調試工具,而不使用 JDWP 和 JDI,只使用自己的通訊和命令接口。當然,除非是有特殊的需求,利用已有的實現會使您事半功倍,避免重復發明輪子。
這三個模塊我們會在後續文章中分別詳細介紹,這裡我們簡單介紹它們的主要功能:
Java 虛擬機工具接口(JVMTI)
JVMTI(Java Virtual Machine Tool Interface)即指 Java 虛擬機工具接口,它是一套由虛擬機直接提供的 native 接口,它處於整個 JPDA 體系的最底層,所有調試功能本質上都需要通過 JVMTI 來提供。通過這些接口,開發人員不僅調試在該虛擬機上運行的 Java 程序,還能查看它們運行的狀態,設置回調函數,控制某些環境變量,從而優化程序性能。我們知道,JVMTI 的前身是 JVMDI 和 JVMPI,它們原來分別被用於提供調試 Java 程序以及 Java 程序調節性能的功能。在 J2SE 5.0 之後 JDK 取代了 JVMDI 和 JVMPI 這兩套接口,JVMDI 在最新的 Java SE 6 中已經不提供支持,而 JVMPI 也計劃在 Java SE 7 後被徹底取代。
Java 調試線協議(JDWP)
JDWP(Java Debug Wire Protocol)是一個為 Java 調試而設計的一個通訊交互協議,它定義了調試器和被調試程序之間傳遞的信息的格式。在 JPDA 體系中,作為前端(front-end)的調試者(debugger)進程和後端(back-end)的被調試程序(debuggee)進程之間的交互數據的格式就是由 JDWP 來描述的,它詳細完整地定義了請求命令、回應數據和錯誤代碼,保證了前端和後端的 JVMTI 和 JDI 的通信通暢。比如在 Sun 公司提供的實現中,它提供了一個名為 jdwp.dll(jdwp.so)的動態鏈接庫文件,這個動態庫文件實現了一個 Agent,它會負責解析前端發出的請求或者命令,並將其轉化為 JVMTI 調用,然後將 JVMTI 函數的返回值封裝成 JDWP 數據發還給後端。
另外,這裡需要注意的是 JDWP 本身並不包括傳輸層的實現,傳輸層需要獨立實現,但是 JDWP 包括了和傳輸層交互的嚴格的定義,就是說,JDWP 協議雖然不規定我們是通過 EMS 還是快遞運送貨物的,但是它規定了我們傳送的貨物的擺放的方式。在 Sun 公司提供的 JDK 中,在傳輸層上,它提供了 socket 方式,以及在 Windows 上的 shared memory 方式。當然,傳輸層本身無非就是本機內進程間通信方式和遠端通信方式,用戶有興趣也可以按 JDWP 的標准自己實現。
Java 調試接口(JDI)
JDI(Java Debug Interface)是三個模塊中最高層的接口,在多數的 JDK 中,它是由 Java 語言實現的。 JDI 由針對前端定義的接口組成,通過它,調試工具開發人員就能通過前端虛擬機上的調試器來遠程操控後端虛擬機上被調試程序的運行,JDI 不僅能幫助開發人員格式化 JDWP 數據,而且還能為 JDWP 數據傳輸提供隊列、緩存等優化服務。從理論上說,開發人員只需使用 JDWP 和 JVMTI 即可支持跨平台的遠程調試,但是直接編寫 JDWP 程序費時費力,而且效率不高。因此基於 Java 的 JDI 層的引入,簡化了操作,提高了開發人員開發調試程序的效率。
表 1 總結了三個模塊的不同點:
表 1. JPDA 層次比較
模塊 層次 編程語言 作用 JVMTI 底層 C 獲取及控制當前虛擬機狀態 JDWP 中介層 C 定義 JVMTI 和 JDI 交互的數據格式 JDI 高層 Java 提供 Java API 來遠程控制被調試虛擬機
JPDA 實現
每一個虛擬機都應該實現 JVMTI 接口,但是 JDWP 和 JDI 本身與虛擬機並非是不可分的,這三個層之間是通過標准所定義的交互的接口和協議聯系起來的,因此它們可以被獨立替換或取代,但不會影響到整體調試工具的開發和使用。因此,開發和使用自己的 JDWP 和 JDI 接口實現是可能的。
Java 軟件開發包(SDK)標准版裡提供了 JPDA 三個層次的標准實現,事實上,調試工具開發人員還有很多其他開源實現可以選擇,比如 Apache Harmony 提供了 JDWP 的實現。而 JDI,我們可以在 Eclipse 一個子項目 org.eclipse.jdt.debug 裡找到其完整的實現(Harmony 也使用了這套實現,作為其 J2SE 類庫的一部分)。通過標准協議,Eclipse IDE 的調試工具就可以完全在 Harmony 的環境上運行。
Java 調試接口的特點
Java 語言是第一個使用虛擬機概念的流行的編程語言,正是因為虛擬機的存在,使很多事情變得簡單而輕松,掌握了虛擬機,就掌握了內存分配、線程管理、即時優化等等運行態。同樣的,Java 調試的本質,就是和虛擬機打交道,通過操作虛擬機來達到觀察調試我們自己代碼的目的。這個特點決定了 Java 調試接口和以前其他編程語言的巨大區別。
以 C/C++ 的調試為例,目前比較流行的調試工具是 GDB 和微軟的 Visual Studio 自帶的 debugger,在這種 debugger 中,首先,我們必須編譯一個“ debug ”模式的程序,這個會比實際的 release 模式程序大很多。其次,在調試過程中,debugger 將會深層接入程序的運行,掌握和控制運行態的一些信息,並將這些信息及時返回。這種介入對運行的效率和內存占用都有一定的需求。基於這些需求,這些 Debugger 本身事實上是提供了,或者說,創建和管理了一個運行態,因此他們的程序算法比較復雜,個頭都比較大。對於遠端的調試,GDB 也沒有很好的默認實現,當然,C/C++ 在這方面也沒有特別大的需求。
而 Java 則不同,由於 Java 的運行態已經被虛擬機所很好地管理,因此作為 Java 的 Debugger 無需再自己創造一個可控的運行態,而僅僅需要去操作虛擬機就可以了。 Java 的 JPDA 就是一套為調試和優化服務的虛擬機的操作工具,其中,JVMTI 是整合在虛擬機中的接口,JDWP 是一個通訊層,而 JDI 是前端為開發人員准備好的工具和運行庫。
從構架上說,我們可以把 JPDA 看作成是一個 C/S 體系結構的應用,在這個構架下,我們可以方便地通過網絡,在任意的地點調試另外一個虛擬機上的程序,這個就很好地解決了部署和測試的問題,尤其滿足解決了很多網絡時代中的開發應用的需求。前端和後端的分離,也方便用戶開發適合於自己的調試工具。
從效率上看,由於 Java 程序本身就是編譯成字節碼,運行在虛擬機上的,因此調試前後的程序、內存占用都不會有大變化(僅僅是啟動一個 JDWP 所需要的內存),任意程度都可以很好地調試,非常方便。而 JPDA 構架下的幾個組成部分,JDWP 和 JDI 都比較小,主要的工作可以讓虛擬機自己完成。
從靈活性上,Java 調試工具是建立在強大的虛擬機上的,因此,很多前沿的應用,比如動態編譯運行,字節碼的實時替換等等,都可以通過對虛擬機的改進而得到實現。隨著虛擬機技術的逐步發展和深入,各種不同種類,不同應用領域中虛擬機的出現,各種強大的功能的加入,給我們的調試工具也帶來很多新的應用。
總而言之,一個先天的,可控的運行態給 Java 的調試工作,給 Java 調試接口帶來了極大的優勢和便利。通過 JPDA 這個標准,我們可以從虛擬機中得到我們所需要的信息,完成我們所希望的操作,更好地開發我們的程序。
結束語
本文簡單介紹了 JPDA 的三個模塊以及它們如何和其它層次交互,讓讀者在整體上對 JPDA 體系有了一個直觀的了解,從而方便後面針對每個模塊具體介紹的學習,這裡我們學習到:
JPDA 定義了一套如何開發調試工具的接口和規范。
JPDA 由三個獨立的模塊 JVMTI、JDWP、JDI 組成。
調試者通過 JDI 發送接受調試命令。
JDWP 定義調試者和被調試者交流數據的格式。
JVMTI 可以控制當前虛擬機運行狀態。
除了標准實現,JPDA 還有許多開源實現供使用。
Java 調試工具的優點。