天天看點

深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作

(源自:http://www.ibm.com/developerworks/cn/java/j-lo-jpda3/)

深入 Java 調試體系,第 3 部分: JDWP 協定及實作

深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
文檔選項
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
列印本頁
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
将此頁作為電子郵件發送

級别: 中級

虞 俊傑, 軟體工程師 , IBM

徐 睿智, 軟體工程師 , IBM

2009 年 9 月 03 日

JPDA(Java Platform Debugger Architecture) 是 Java 平台調試體系結構的縮寫,通過 JPDA 提供的 API,開發人員可以友善靈活的搭建 Java 調試應用程式。JPDA 主要由三個部分組成:Java 虛拟機工具接口(JVMTI),Java 調試線協定(JDWP),以及 Java 調試接口(JDI),本系列将會詳細介紹這三個子產品的内部細節、通過執行個體為讀者揭開 JPDA 的面紗。

本文是該系列的第三篇。使用者在使用 Java 語言開發應用程式的時候,往往需要對虛拟機内部的運作狀态進行觀察和調試,那麼調試器是如何對 Java 虛拟機中的資訊進行觀察的呢?這篇文章将會着重介紹 JDWP 協定的具體細節,并通過講解一個 JDWP 的指令以及剖析程式調試過程中斷點的生成過程來為讀者揭示 JDWP 的實作機制。

JDWP 是 Java Debug Wire Protocol 的縮寫,它定義了調試器(debugger)和被調試的 Java 虛拟機(target vm)之間的通信協定。

JDWP 協定介紹

這裡首先要說明一下 debugger 和 target vm。Target vm 中運作着我們希望要調試的程式,它與一般運作的 Java 虛拟機沒有什麼差別,隻是在啟動時加載了 Agent JDWP 進而具備了調試功能。而 debugger 就是我們熟知的調試器,它向運作中的 target vm 發送指令來擷取 target vm 運作時的狀态和控制 Java 程式的執行。Debugger 和 target vm 分别在各自的程序中運作,他們之間的通信協定就是 JDWP。

JDWP 與其他許多協定不同,它僅僅定義了資料傳輸的格式,但并沒有指定具體的傳輸方式。這就意味着一個 JDWP 的實作可以不需要做任何修改就正常工作在不同的傳輸方式上(在 JDWP 傳輸接口中會做詳細介紹)。

JDWP 是語言無關的。理論上我們可以選用任意語言實作 JDWP。然而我們注意到,在 JDWP 的兩端分别是 target vm 和 debugger。Target vm 端,JDWP 子產品必須以 Agent library 的形式在 Java 虛拟機啟動時加載,并且它必須通過 Java 虛拟機提供的 JVMTI 接口實作各種 debug 的功能,是以必須使用 C/C++ 語言編寫。而 debugger 端就沒有這樣的限制,可以使用任意語言編寫,隻要遵守 JDWP 規範即可。JDI(Java Debug Interface)就包含了一個 Java 的 JDWP debugger 端的實作(JDI 将在該系列的下一篇文章中介紹),JDK 中調試工具 jdb 也是使用 JDI 完成其調試功能的。

圖 1. JDWP agent 在調試中扮演的角色

深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作

協定分析

JDWP 大緻分為兩個階段:握手和應答。握手是在傳輸層連接配接建立完成後,做的第一件事:

Debugger 發送 14 bytes 的字元串“JDWP-Handshake”到 target Java 虛拟機

Target Java 虛拟機回複“JDWP-Handshake”

圖 2. JDWP 的握手協定

深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作

握手完成,debugger 就可以向 target Java 虛拟機發送指令了。JDWP 是通過指令(command)和回複(reply)進行通信的,這與 HTTP 有些相似。JDWP 本身是無狀态的,是以對 command 出現的順序并不受限制。

JDWP 有兩種基本的包(packet)類型:指令包(command packet)和回複包(reply packet)。

Debugger 和 target Java 虛拟機都有可能發送 command packet。Debugger 通過發送 command packet 擷取 target Java 虛拟機的資訊以及控制程式的執行。Target Java 虛拟機通過發送 command packet 通知 debugger 某些事件的發生,如到達斷點或是産生異常。

Reply packet 是用來回複 command packet 該指令是否執行成功,如果成功 reply packet 還有可能包含 command packet 請求的資料,比如目前的線程資訊或者變量的值。從 target Java 虛拟機發送的事件消息是不需要回複的。

還有一點需要注意的是,JDWP 是異步的:command packet 的發送方不需要等待接收到 reply packet 就可以繼續發送下一個 command packet。

Packet 的結構

Packet 分為標頭(header)和資料(data)兩部分組成。標頭部分的結構和長度是固定,而資料部分的長度是可變的,具體内容視 packet 的内容而定。Command packet 和 reply packet 的標頭長度相同,都是 11 個 bytes,這樣更有利于傳輸層的抽象和實作。

Command packet 的 header 的結構 :

圖 3. JDWP command packet 結構

深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作

Length 是整個 packet 的長度,包括 length 部分。因為標頭的長度是固定的 11 bytes,是以如果一個 command packet 沒有資料部分,則 length 的值就是 11。

Id 是一個唯一值,用來标記和識别 reply 所屬的 command。Reply packet 與它所回複的 command packet 具有相同的 Id,異步的消息就是通過 Id 來配對識别的。

Flags 目前對于 command packet 值始終是 0。

Command Set 相當于一個 command 的分組,一些功能相近的 command 被分在同一個 Command Set 中。Command Set 的值被劃分為 3 個部分:

0-63: 從 debugger 發往 target Java 虛拟機的指令

64 – 127: 從 target Java 虛拟機發往 debugger 的指令

128 – 256: 預留的自定義和擴充指令

Reply packet 的 header 的結構:

圖 4. JDWP reply packet 結構

深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作

Length、Id 作用與 command packet 中的一樣。

Flags 目前對于 reply packet 值始終是 0x80。我們可以通過 Flags 的值來判斷接收到的 packet 是 command 還是 reply。

Error Code 用來表示被回複的指令是否被正确執行了。零表示正确,非零表示執行錯誤。

Data 的内容和結構依據不同的 command 和 reply 都有所不同。比如請求一個對象成員變量值的 command,它的 data 中就包含該對象的 id 和成員變量的 id。而 reply 中則包含該成員變量的值。

JDWP 還定義了一些資料類型專門用來傳遞 Java 相關的資料資訊。下面列舉了一些資料類型,詳細的說明參見 [1]

表 1. JDWP 中資料類型介紹

名稱 長度 說明
byte 1 byte byte 值。
boolean 1 byte 布爾值,0 表示假,非零表示真。
int 4 byte 4 位元組有符号整數。
long 8 byte 8 位元組有符号整數。
objectID 依據 target Java 虛拟機而定,最大 8 byte Target Java 虛拟機中對象(object)的唯一 ID。這個值在整個 JDWP 的會話中不會被重用,始終指向同一個對象,即使該對象已經被 GC 回收(引用被回收的對象将傳回 INVALID_OBJECT 錯誤。
Tagged-objectID objectID 的長度加 1 第一個 byte 表示對象的類型,比如,整型,字元串,類等等。緊接着是一個 objectID。
threadID 同 objectID 的長度 表示 Target Java 虛拟機中的一個線程對象
stringID 同 objectID 的長度 表示 Target Java 虛拟機中的一字元串對象
referenceTypeID 同 objectID 的長度 表示 Target Java 虛拟機中的一個引用類型對象,即類(class)的唯一 ID。
classID 同 objectID 的長度 表示 Target Java 虛拟機中的一個類對象。
methodID 依據 target Java 虛拟機而定,最大 8 byte Target Java 虛拟機某個類中的方法的唯一 ID。methodID 必須在他所屬類和所屬類的所有子類中保持唯一。從整個 Java 虛拟機來看它并不是唯一的。methodID 與它所屬類的 referenceTypeID 一起在整個 Java 虛拟機中是唯一的。
fieldID 依據 target Java 虛拟機而定,最大 8 byte 與 methodID 類似,Target Java 虛拟機某個類中的成員的唯一 ID。
frameID 依據 target Java 虛拟機而定,最大 8 byte Java 中棧中的每一層方法調用都會生成一個 frame。frameID 在整個 target Java 虛拟機中是唯一的,并且隻線上程挂起(suspended)的時候有效。
location 依據 target Java 虛拟機而定,最大 8 byte 一個可執行的位置。Debugger 用它來定位 stepping 時在源代碼中的位置。

JDWP 傳輸接口(Java Debug Wire Protocol Transport Interface)

前面提到 JDWP 的定義是與傳輸層獨立的,但如何使 JDWP 能夠無縫的使用不同的傳輸實作,而又無需修改 JDWP 本身的代碼? JDWP 傳輸接口(Java Debug Wire Protocol Transport Interface)為我們解決了這個問題。

JDWP 傳輸接口定義了一系列的方法用來定義 JDWP 與傳輸層實作之間的互動方式。首先傳輸層的必須以動态連結庫的方式實作,并且暴露一系列的标準接口供 JDWP 使用。與 JNI 和 JVMTI 類似,通路傳輸層也需要一個環境指針(jdwpTransport),通過這個指針可以通路傳輸層提供的所有方法。

當 JDWP agent 被 Java 虛拟機加載後,JDWP 會根據參數去加載指定的傳輸層實作(Sun 的 JDK 在 Windows 提供 socket 和 share memory 兩種傳輸方式,而在 Linux 上隻有 socket 方式)。傳輸層實作的動态連結庫實作必須暴露 jdwpTransport_OnLoad 接口,JDWP agent 在加載傳輸層動态連結庫後會調用該接口進行傳輸層的初始化。接口定義如下:

JNIEXPORT jint JNICALL 
jdwpTransport_OnLoad(JavaVM *jvm, 
    jdwpTransportCallback *callback, 
    jint version, 
    jdwpTransportEnv** env); 
      
callback 參數指向一個記憶體管理的函數表,傳輸層用它來進行記憶體的配置設定和釋放,結構定義如下:
typedef struct jdwpTransportCallback { 
    void* (*alloc)(jint numBytes); 
    void (*free)(void *buffer); 
} jdwpTransportCallback; 
      

env 參數是環境指針,指向的函數表由傳輸層初始化。

JDWP 傳輸層定義的接口主要分為兩類:連接配接管理和 I/O 操作。

連接配接管理

連接配接管理接口主要負責連接配接的建立和關閉。一個連接配接為 JDWP 和 debugger 提供了可靠的資料流。Packet 被接收的順序嚴格的按照被寫入連接配接的順序。

連接配接的建立是雙向的,即 JDWP 可以主動去連接配接 debugger 或者 JDWP 等待 debugger 的連接配接。對于主動去連接配接 debugger,需要調用方法 Attach,定義如下:

jdwpTransportError 
Attach(jdwpTransportEnv* env, const char* address, 
    jlong attachTimeout, jlong handshakeTimeout) 
      

在連接配接建立後,會立即進行握手操作,確定對方也在使用 JDWP。是以方法參數中分别指定了 attch 和握手的逾時時間。

address 參數因傳輸層的實作不同而有不同的格式。對于 socket,address 是主機位址;對于 share memory 則是共享記憶體的名稱。

JDWP 等待 debugger 連接配接的方式,首先需要調用 StartListening 方法,定義如下:

jdwpTransportError 
StartListening(jdwpTransportEnv* env, const char* address, 
    char** actualAddress) 
      
該方法将使 JDWP 處于監聽狀态,随後調用 Accept 方法接收連接配接:
jdwpTransportError 
Accept(jdwpTransportEnv* env, jlong acceptTimeout, jlong 
    handshakeTimeout) 
      

與 Attach 方法類似,在連接配接建立後,會立即進行握手操作。

I/O 操作

I/O 操作接口主要是負責從傳輸層讀寫 packet。有 ReadPacket 和 WritePacket 兩個方法:

jdwpTransportError 
ReadPacket(jdwpTransportEnv* env, jdwpPacket* packet) 
jdwpTransportError 
WritePacket(jdwpTransportEnv* env, const jdwpPacket* packet) 
      
參數 packet 是要被讀寫的 packet,其結構 jdwpPacket 與我們開始提到的 JDWP packet 結構一緻,定義如下:
typedef struct { 
    jint len; 		 // packet length 
    jint id; 		 // packet id 
    jbyte flags; 	 // value is 0 
    jbyte cmdSet; 	 // command set 
    jbyte cmd; 		 // command in specific command set 
    jbyte *data; 	 // data carried by packet 
} jdwpCmdPacket; 
typedef struct { 
    jint len; 		 // packet length 
    jint id; 		 // packet id 
    jbyte flags; 	 // value 0x80 
    jshort errorCode; 	 // error code 
    jbyte *data; 	 // data carried by packet 
} jdwpReplyPacket; 
typedef struct jdwpPacket { 
    union { 
        jdwpCmdPacket cmd; 
        jdwpReplyPacket reply; 
    } type; 
} jdwpPacket; 
      

JDWP 的指令實作機制

下面将通過講解一個 JDWP 指令的執行個體來介紹 JDWP 指令的實作機制。JDWP 作為一種協定,它的作用就在于充當了調試器與 Java 虛拟機的溝通橋梁。通俗點講,調試器在調試過程中需要不斷向 Java 虛拟機查詢各種資訊,那麼 JDWP 就規定了查詢的具體方式。

在 Java 6.0 中,JDWP 包含了 18 組指令集合,其中每個指令集合又包含了若幹條指令。那麼這些指令是如何實作的呢?下面我們先來看一個最簡單的 VirtualMachine(指令集合 1)的 Version 指令,以此來剖析其中的實作細節。

因為 JDWP 在整個 JPDA 架構中處于相對底層的位置(在前兩篇本系列文章中有具體說明),我們無法在現實應用中來為大家示範 JDWP 的單個指令的執行過程。在這裡我們通過一個針對該指令的 Java 測試用例來說明。

CommandPacket packet = new CommandPacket( 
    JDWPCommands.VirtualMachineCommandSet.CommandSetID, 
    JDWPCommands.VirtualMachineCommandSet.VersionCommand); 
        
ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet); 
String description = reply.getNextValueAsString(); 
int    jdwpMajor   = reply.getNextValueAsInt(); 
int    jdwpMinor   = reply.getNextValueAsInt(); 
String vmVersion   = reply.getNextValueAsString(); 
String vmName      = reply.getNextValueAsString(); 
logWriter.println("description/t= " + description); 
logWriter.println("jdwpMajor/t= " + jdwpMajor); 
logWriter.println("jdwpMinor/t= " + jdwpMinor); 
logWriter.println("vmVersion/t= " + vmVersion); 
logWriter.println("vmName/t/t= " + vmName); 
      

這裡先簡單介紹一下這段代碼的作用。

首先,我們會建立一個 VirtualMachine 的 Version 指令的指令包執行個體 packet。你可能已經注意到,該指令包主要就是配置了兩個參數 : CommandSetID 和 VersionComamnd,它們的值均為 1。表明我們想執行的指令是屬于指令集合 1 的指令 1,即 VirtualMachine 的 Version 指令。

然後在 performCommand 方法中我們發送了該指令并收到了 JDWP 的回複包 reply。通過解析 reply,我們得到了該指令的回複資訊。

description = Java 虛拟機 version 1.6.0 (IBM J9 VM, J2RE 1.6.0 IBM J9 2.4 Windows XP x86-32 
jvmwi3260sr5-20090519_35743 (JIT enabled, AOT enabled) 
J9VM - 20090519_035743_lHdSMr 
JIT  - r9_20090518_2017 
GC   - 20090417_AA, 2.4) 
jdwpMajor 	 = 1 
jdwpMinor 	 = 6 
vmVersion 	 = 1.6.0 
vmName 		 = IBM J9 VM 
      

測試用例的執行結果顯示,我們通過該指令獲得了 Java 虛拟機的版本資訊,這正是 VirtualMachine 的 Version 指令的作用。

前面已經提到,JDWP 接收到的是調試器發送的指令包,傳回的就是回報資訊的回複包。在這個例子中,我們模拟的調試器會發送 VirtualMachine 的 Version 指令。JDWP 在執行完該指令後就向調試器傳回 Java 虛拟機的版本資訊。

傳回資訊的包内容同樣是在 JDWP Spec 裡面規定的。比如本例中的回複包,Spec 中的描述如下(測試用例中的回複包解析就是參照這個規定的 ):

表 2. VirtualMachine 的 Version 指令傳回包定義

類型 名稱 說明
string description VM version 的文字描述資訊。
int jdwpMajor JDWP 主版本号。
int jdwpMinor JDWP 次版本号。
string vmVersion VM JRE 版本,也就是 java.version 屬性值。
string vmName VM 的名稱,也就是 java.vm.name 屬性值。

通過這個簡單的例子,相信大家對 JDWP 的指令已經有了一個大體的了解。 那麼在 JDWP 内部是如何處理接收到的指令并傳回回複包的呢?下面以 Apache Harmony 的 JDWP 為例,為大家介紹其内部的實作架構。

圖 5. JDWP 架構圖

深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作

如圖所示,JDWP 接收和發送的包都會經過 TransportManager 進行處理。JDWP 的應用層與傳輸層是獨立的,就在于 TransportManager 調用的是 JDWP 傳輸接口(Java Debug Wire Protocol Transport Interface),是以無需關心底層網絡的具體傳輸實作。TransportManager 的主要作用就是充當 JDWP 與外界通訊的資料包的中轉站,負責将 JDWP 的指令包在接收後進行解析或是對回複包在發送前進行打包,進而使 JDWP 能夠專注于應用層的實作。

對于收到的指令包,TransportManager 處理後會轉給 PacketDispatcher,進一步封裝後會繼續轉到 CommandDispatcher。然後,CommandDispatcher 會根據指令中提供的指令組号和指令号建立一個具體的 CommandHandler 來處理 JDWP 指令。

其中,CommandHandler 才是真正執行 JDWP 指令的類。我們會為每個 JDWP 指令都定義一個相對應的 CommandHandler 的子類,當接收到某個指令時,就會建立處理該指令的 CommandHandler 的子類的執行個體來作具體的處理。

圖 6. JDWP 指令處理流程

深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作

單線程執行的指令

上圖就是一個指令的處理流程圖。可以看到,對于一個可以直接在該線程中完成的指令(我們稱為單線程執行的指令),一般其内部會調用 JVMTI 方法和 JNI 方法來真正對 Java 虛拟機進行操作。

例如,VirtualMachine 的 Version 指令中,對于 vmVersion 和 vmName 屬性,我們可以通過 JNI 來調用 Java 方法 System.getProperty 來擷取。然後,JDWP 将回複包中所需要的結果封裝到包中後交由 TransportManager 來進行後續操作。

多線程執行的指令

對于一些較為複雜的指令,是無法在 CommandHandler 子類的處理線程中完成的。例如,ClassType 的 InvokeMethod 指令,它會要求在指定的某個線程中執行一個靜态方法。顯然,CommandHandler 子類的目前線程并不是所要求的線程。

這時,JDWP 線程會先把這個請求先放到一個清單中,然後等待,直到所要求的線程執行完那個靜态方法後,再把結果傳回給調試器。

JDWP 的事件處理機制

前面介紹的 VirtualMachine 的 Version 指令過程非常簡單,就是一個查詢和資訊傳回的過程。在實際調試過程中,一個 JDI 的指令往往會有數條這類簡單的查詢指令參與,而且會涉及到很多更為複雜的指令。要了解更為複雜的 JDWP 指令實作機制,就必須介紹 JDWP 的事件處理機制。

在 Java 虛拟機中,我們會接觸到許多事件,例如 VM 的初始化,類的裝載,異常的發生,斷點的觸發等等。那麼這些事件調試器是如何通過 JDWP 來獲知的呢?下面,我們通過介紹在調試過程中斷點的觸發是如何實作的,來為大家揭示其中的實作機制。

在這裡,我們任意調試一段 Java 程式,并在某一行中加入斷點。然後,我們執行到該斷點,此時所有 Java 線程都處于 suspend 狀态。這是很常見的斷點觸發過程。為了記錄在此過程中 JDWP 的行為,我們使用了一個開啟了 trace 資訊的 JDWP。雖然這并不是一個複雜的操作,但整個 trace 資訊也有幾千行。

可見,作為相對底層的 JDWP,其實際處理的指令要比想象的多許多。為了介紹 JDWP 的事件處理機制,我們挑選了其中比較重要的一些 trace 資訊來說明:

[RequestManager.cpp:601] AddRequest: event=BREAKPOINT[2], req=48, modCount=1, policy=1 
[RequestManager.cpp:791] GenerateEvents: event #0: kind=BREAKPOINT, req=48 
[RequestManager.cpp:1543] HandleBreakpoint: BREAKPOINT events: count=1, suspendPolicy=1, 
                          location=0 
[RequestManager.cpp:1575] HandleBreakpoint: post set of 1 
[EventDispatcher.cpp:415] PostEventSet -- wait for release on event: thread=4185A5A0, 
                          name=(null), eventKind=2 
[EventDispatcher.cpp:309] SuspendOnEvent -- send event set: id=3, policy=1 
[EventDispatcher.cpp:334] SuspendOnEvent -- wait for thread on event: thread=4185A5A0, 
                          name=(null) 
[EventDispatcher.cpp:349] SuspendOnEvent -- suspend thread on event: thread=4185A5A0, 
                          name=(null) 
[EventDispatcher.cpp:360] SuspendOnEvent -- release thread on event: thread=4185A5A0, 
                          name=(null) 
      

首先,調試器需要發起一個斷點的請求,這是通過 JDWP 的 Set 指令完成的。在 trace 中,我們看到 AddRequest 就是做了這件事。可以清楚的發現,調試器請求的是一個斷點資訊(event=BREAKPOINT[2])。

在 JDWP 的實作中,這一過程表現為:在 Set 指令中會生成一個具體的 request, JDWP 的 RequestManager 會記錄這個 request(request 中會包含一些過濾條件,當事件發生時 RequestManager 會過濾掉不符合預先設定條件的事件),并通過 JVMTI 的 SetEventNotificationMode 方法使這個事件觸發生效(否則事件發生時 Java 虛拟機不會報告)。

圖 7. JDWP 事件處理流程

深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作

當斷點發生時,Java 虛拟機就會調用 JDWP 中預先定義好的處理該事件的回調函數。在 trace 中,HandleBreakpoint 就是我們在 JDWP 中定義好的處理斷點資訊的回調函數。它的作用就是要生成一個 JDWP 端所描述的斷點事件來告知調試器(Java 虛拟機隻是觸發了一個 JVMTI 的消息)。

由于斷點的事件在調試器申請時就要求所有 Java 線程在斷點觸發時被 suspend,那這一步由誰來完成呢?這裡要談到一個細節問題,HandleBreakpoint 作為一個回調函數,其執行線程其實就是斷點觸發的 Java 線程。

顯然,我們不應該由它來負責 suspend 所有 Java 線程。

原因很簡單,我們還有一步工作要做,就是要把該斷點觸發資訊傳回給調試器。如果我們先傳回資訊,然後 suspend 所有 Java 線程,這就無法保證在調試器收到資訊時所有 Java 線程已經被 suspend。

反之,先 Suspend 了所有 Java 線程,誰來負責發送資訊給調試器呢?

為了解決這個問題,我們通過 JDWP 的 EventDispatcher 線程來幫我們 suspend 線程和發送資訊。實作的過程是,我們讓觸發斷點的 Java 線程來 PostEventSet(trace 中可以看到),把生成的 JDWP 事件放到一個隊列中,然後就開始等待。由 EventDispatcher 線程來負責從隊列中取出 JDWP 事件,并根據事件中的設定,來 suspend 所要求的 Java 線程并發送出該事件。

在這裡,我們在事件觸發的 Java 線程和 EventDispatcher 線程之間添加了一個同步機制,當事件發送出去後,事件觸發的 Java 線程會把 JDWP 中的該事件删除,到這裡,整個 JDWP 事件處理就完成了。

結語

我們在調試 Java 程式的時候,往往需要對虛拟機内部的運作狀态進行觀察和調試,JDWP Agent 就充當了調試器與 Java 虛拟機的溝通橋梁。它的工作原理簡單來說就是對于 JDWP 指令的處理和事件的管理。由于 JDWP 在 JPDA 中處于相對底層的位置,調試器發出一個 JDI 指令,往往要通過很多 JDWP 指令來完成。在下一篇文章中,我們将具體介紹 JDI 是如何工作在 JDWP 協定上的。

參考資料

學習

  • 檢視 本系列 完整的文章清單。
  • 在 JPDA 官方的首頁上,您可以檢視關于 JPDA 标準實作的細節。
  • 您可以參與 JPDA 官方論壇上的參與。
  • Eclipse IDE是一個非常受歡迎的開源開發工具,它內建了對 JPDA 的支援。
  • 參閱 JDWP 協定規範:Specification of Java Debug Wire Protocol。
  • 參閱 JDWP 協定接口規範:Specification of Java Debug Wire Protocol Interface。
  • developerWorks Java 技術專區:查找數百篇有關 Java 程式設計各方面的文章。
獲得産品和技術
  • 下載下傳 IBM 軟體試用版,體驗強大的 DB2®,Lotus®,Rational®,Tivoli®和 WebSphere®軟體。
讨論
  • 檢視 developerWorks 部落格 的最新資訊。
作者簡介
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
虞俊傑,就職于 IBM 中國開發中心 Harmony 開發團隊,專注于對 Java 類庫和運作态的研究和開發。他對于 Java 和 C++ 程式設計都有濃厚的興趣。
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
深入 Java 調試體系,第 3 部分深入 Java 調試體系,第 3 部分: JDWP 協定及實作
徐睿智,就職于 IBM 中國開發中心 Harmony 開發團隊,專注于對 Java 類庫和運作态的研究和開發。他對于多核和并發程式設計都有濃厚的興趣。