JDWP 協(xié)議及實現(xiàn)

什么是JDWP 甸鸟?

JDWP 是 Java Debug Wire Protocol 的縮寫前翎,它定義了調(diào)試器(debugger)和被調(diào)試的 Java 虛擬機(target vm)之間的通信協(xié)議。

JDWP 協(xié)議介紹

這里首先要說明一下 debugger 和 target vm。Target vm 中運行著我們希望要調(diào)試的程序,它與一般運行的 Java 虛擬機沒有什么區(qū)別,只是在啟動時加載了 Agent JDWP 從而具備了調(diào)試功能磨淌。而 debugger 就是我們熟知的調(diào)試器硫豆,它向運行中的 target vm 發(fā)送命令來獲取 target vm 運行時的狀態(tài)和控制 Java 程序的執(zhí)行。Debugger 和 target vm 分別在各自的進程中運行无拗,他們之間的通信協(xié)議就是 JDWP带到。

JDWP 與其他許多協(xié)議不同,它僅僅定義了數(shù)據(jù)傳輸?shù)母袷接⑷荆]有指定具體的傳輸方式揽惹。這就意味著一個 JDWP 的實現(xiàn)可以不需要做任何修改就正常工作在不同的傳輸方式上(在 JDWP 傳輸接口中會做詳細(xì)介紹)。

JDWP 是語言無關(guān)的四康。理論上我們可以選用任意語言實現(xiàn) JDWP搪搏。然而我們注意到,在 JDWP 的兩端分別是 target vm 和 debugger闪金。Target vm 端疯溺,JDWP 模塊必須以 Agent library 的形式在 Java 虛擬機啟動時加載,并且它必須通過 Java 虛擬機提供的 JVMTI 接口實現(xiàn)各種 debug 的功能哎垦,所以必須使用 C/C++ 語言編寫囱嫩。而 debugger 端就沒有這樣的限制,可以使用任意語言編寫漏设,只要遵守 JDWP 規(guī)范即可墨闲。JDI(Java Debug Interface)就包含了一個 Java 的 JDWP debugger 端的實現(xiàn)(JDI 將在該系列的下一篇文章中介紹),JDK 中調(diào)試工具 jdb 也是使用 JDI 完成其調(diào)試功能的郑口。

圖 1. JDWP agent 在調(diào)試中扮演的角色


DWP agent 在調(diào)試中扮演的角色

協(xié)議分析

JDWP 大致分為兩個階段:握手和應(yīng)答鸳碧。握手是在傳輸層連接建立完成后,做的第一件事:
Debugger 發(fā)送 14 bytes 的字符串“JDWP-Handshake”到 target Java 虛擬機
Target Java 虛擬機回復(fù)“JDWP-Handshake”
圖 2. JDWP 的握手協(xié)議

JDWP 的握手協(xié)議

握手完成潘酗,debugger 就可以向 target Java 虛擬機發(fā)送命令了杆兵。JDWP 是通過命令(command)和回復(fù)(reply)進行通信的,這與 HTTP 有些相似仔夺。JDWP 本身是無狀態(tài)的琐脏,因此對 command 出現(xiàn)的順序并不受限制。JDWP 有兩種基本的包(packet)類型:命令包(command packet)和回復(fù)包(reply packet)缸兔。

Debugger 和 target Java 虛擬機都有可能發(fā)送 command packet日裙。Debugger 通過發(fā)送 command packet 獲取 target Java 虛擬機的信息以及控制程序的執(zhí)行。Target Java 虛擬機通過發(fā)送 command packet 通知 debugger 某些事件的發(fā)生惰蜜,如到達斷點或是產(chǎn)生異常昂拂。

Reply packet 是用來回復(fù) command packet 該命令是否執(zhí)行成功,如果成功 reply packet 還有可能包含 command packet 請求的數(shù)據(jù)抛猖,比如當(dāng)前的線程信息或者變量的值格侯。從 target Java 虛擬機發(fā)送的事件消息是不需要回復(fù)的鼻听。還有一點需要注意的是,JDWP 是異步的:command packet 的發(fā)送方不需要等待接收到 reply packet 就可以繼續(xù)發(fā)送下一個 command packet联四。

Packet 的結(jié)構(gòu)

Packet 分為包頭(header)和數(shù)據(jù)(data)兩部分組成撑碴。包頭部分的結(jié)構(gòu)和長度是固定,而數(shù)據(jù)部分的長度是可變的朝墩,具體內(nèi)容視 packet 的內(nèi)容而定醉拓。Command packet 和 reply packet 的包頭長度相同,都是 11 個 bytes收苏,這樣更有利于傳輸層的抽象和實現(xiàn)亿卤。

  • Command packet 的 header 的結(jié)構(gòu) :
  • 圖 3. JDWP command packet 結(jié)構(gòu)


    JDWP command packet 結(jié)構(gòu)

Length 是整個 packet 的長度,包括 length 部分鹿霸。因為包頭的長度是固定的 11 bytes排吴,所以如果一個 command packet 沒有數(shù)據(jù)部分,則 length 的值就是 11杜跷。

Id 是一個唯一值傍念,用來標(biāo)記和識別 reply 所屬的 command矫夷。Reply packet 與它所回復(fù)的 command packet 具有相同的 Id葛闷,異步的消息就是通過 Id 來配對識別的。
Flags 目前對于 command packet 值始終是 0双藕。

Command Set 相當(dāng)于一個 command 的分組淑趾,一些功能相近的 command 被分在同一個 Command Set 中。Command Set 的值被劃分為 3 個部分:

0-63: 從 debugger 發(fā)往 target Java 虛擬機的命令
64 – 127: 從 target Java 虛擬機發(fā)往 debugger 的命令
128 – 256: 預(yù)留的自定義和擴展命令

  • Reply packet 的 header 的結(jié)構(gòu):
  • 圖 4. JDWP reply packet 結(jié)構(gòu)


    JDWP reply packet 結(jié)構(gòu)

Length忧陪、Id 作用與 command packet 中的一樣扣泊。
Flags 目前對于 reply packet 值始終是 0x80。我們可以通過 Flags 的值來判斷接收到的 packet 是 command 還是 reply嘶摊。
Error Code 用來表示被回復(fù)的命令是否被正確執(zhí)行了延蟹。零表示正確,非零表示執(zhí)行錯誤叶堆。

Data 的內(nèi)容和結(jié)構(gòu)依據(jù)不同的 command 和 reply 都有所不同阱飘。比如請求一個對象成員變量值的 command,它的 data 中就包含該對象的 id 和成員變量的 id虱颗。而 reply 中則包含該成員變量的值沥匈。

JDWP 還定義了一些數(shù)據(jù)類型專門用來傳遞 Java 相關(guān)的數(shù)據(jù)信息。下面列舉了一些數(shù)據(jù)類型忘渔,詳細(xì)的說明參見 [1]

  • 表 1. JDWP 中數(shù)據(jù)類型介紹
名稱 長度 說明
byte 1 byte byte 值高帖。
boolean 1 byte 布爾值,0 表示假畦粮,非零表示真散址。
int 4 byte 4 字節(jié)有符號整數(shù)乖阵。
long 8 byte 8 字節(jié)有符號整數(shù)。
objectID 依據(jù) target Java 虛擬機而定预麸,最大 8 byte Target Java 虛擬機中對象(object)的唯一 ID义起。這個值在整個 JDWP 的會話中不會被重用,始終指向同一個對象师崎,即使該對象已經(jīng)被 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 依據(jù) target Java 虛擬機而定,最大 8 byte Target Java 虛擬機某個類中的方法的唯一 ID递胧。methodID 必須在他所屬類和所屬類的所有子類中保持唯一碑韵。從整個 Java 虛擬機來看它并不是唯一的。methodID 與它所屬類的 referenceTypeID 一起在整個 Java 虛擬機中是唯一的缎脾。
fieldID 依據(jù) target Java 虛擬機而定祝闻,最大 8 byte 與 methodID 類似,Target Java 虛擬機某個類中的成員的唯一 ID遗菠。
frameID 依據(jù) target Java 虛擬機而定联喘,最大 8 byte Java 中棧中的每一層方法調(diào)用都會生成一個 frame。frameID 在整個 target Java 虛擬機中是唯一的辙纬,并且只在線程掛起(suspended)的時候有效豁遭。
location 依據(jù) target Java 虛擬機而定,最大 8 byte 一個可執(zhí)行的位置贺拣。Debugger 用它來定位 stepping 時在源代碼中的位置蓖谢。

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

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

JDWP 傳輸接口定義了一系列的方法用來定義 JDWP 與傳輸層實現(xiàn)之間的交互方式昂儒。首先傳輸層的必須以動態(tài)鏈接庫的方式實現(xiàn)沟使,并且暴露一系列的標(biāo)準(zhǔn)接口供 JDWP 使用。與 JNI 和 JVMTI 類似渊跋,訪問傳輸層也需要一個環(huán)境指針(jdwpTransport)腊嗡,通過這個指針可以訪問傳輸層提供的所有方法着倾。

當(dāng) JDWP agent 被 Java 虛擬機加載后,JDWP 會根據(jù)參數(shù)去加載指定的傳輸層實現(xiàn)(Sun 的 JDK 在 Windows 提供 socket 和 share memory 兩種傳輸方式燕少,而在 Linux 上只有 socket 方式)卡者。傳輸層實現(xiàn)的動態(tài)鏈接庫實現(xiàn)必須暴露 jdwpTransport_OnLoad 接口,JDWP agent 在加載傳輸層動態(tài)鏈接庫后會調(diào)用該接口進行傳輸層的初始化客们。接口定義如下:

JNIEXPORT jint JNICALL 
jdwpTransport_OnLoad(JavaVM *jvm, 
    jdwpTransportCallback *callback, 
    jint version, 
    jdwpTransportEnv** env);
  • callback 參數(shù)指向一個內(nèi)存管理的函數(shù)表崇决,傳輸層用它來進行內(nèi)存的分配和釋放,結(jié)構(gòu)定義如下:
typedef struct jdwpTransportCallback { 
    void* (*alloc)(jint numBytes); 
    void (*free)(void *buffer); 
} jdwpTransportCallback;
  • env 參數(shù)是環(huán)境指針底挫,指向的函數(shù)表由傳輸層初始化恒傻。
    JDWP 傳輸層定義的接口主要分為兩類:連接管理和 I/O 操作

連接管理

連接管理接口主要負(fù)責(zé)連接的建立和關(guān)閉。一個連接為 JDWP 和 debugger 提供了可靠的數(shù)據(jù)流建邓。Packet 被接收的順序嚴(yán)格的按照被寫入連接的順序盈厘。

連接的建立是雙向的,即 JDWP 可以主動去連接 debugger 或者 JDWP 等待 debugger 的連接官边。對于主動去連接 debugger沸手,需要調(diào)用方法 Attach,定義如下:

jdwpTransportError 
Attach(jdwpTransportEnv* env, const char* address, 
    jlong attachTimeout, jlong handshakeTimeout)
  • 該方法將使 JDWP 處于監(jiān)聽狀態(tài)注簿,隨后調(diào)用 Accept 方法接收連接:
jdwpTransportError 
Accept(jdwpTransportEnv* env, jlong acceptTimeout, jlong 
    handshakeTimeout)
  • 與 Attach 方法類似契吉,在連接建立后,會立即進行握手操作滩援。

I/O 操作

  • I/O 操作接口主要是負(fù)責(zé)從傳輸層讀寫 packet栅隐。有 ReadPacket 和 WritePacket 兩個方法:
jdwpTransportError 
ReadPacket(jdwpTransportEnv* env, jdwpPacket* packet) 
 
jdwpTransportError 
WritePacket(jdwpTransportEnv* env, const jdwpPacket* packet)
  • 參數(shù) packet 是要被讀寫的 packet,其結(jié)構(gòu) jdwpPacket 與我們開始提到的 JDWP packet 結(jié)構(gòu)一致玩徊,定義如下:
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 的命令實現(xiàn)機制

下面將通過講解一個 JDWP 命令的實例來介紹 JDWP 命令的實現(xiàn)機制。JDWP 作為一種協(xié)議谨究,它的作用就在于充當(dāng)了調(diào)試器與 Java 虛擬機的溝通橋梁恩袱。通俗點講,調(diào)試器在調(diào)試過程中需要不斷向 Java 虛擬機查詢各種信息胶哲,那么 JDWP 就規(guī)定了查詢的具體方式畔塔。

在 Java 6.0 中,JDWP 包含了 18 組命令集合鸯屿,其中每個命令集合又包含了若干條命令澈吨。那么這些命令是如何實現(xiàn)的呢?下面我們先來看一個最簡單的 VirtualMachine(命令集合 1)的 Version 命令寄摆,以此來剖析其中的實現(xiàn)細(xì)節(jié)谅辣。

因為 JDWP 在整個 JPDA 框架中處于相對底層的位置(在前兩篇本系列文章中有具體說明),我們無法在現(xiàn)實應(yīng)用中來為大家演示 JDWP 的單個命令的執(zhí)行過程婶恼。在這里我們通過一個針對該命令的 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);

這里先簡單介紹一下這段代碼的作用柏副。
首先,我們會創(chuàng)建一個 VirtualMachine 的 Version 命令的命令包實例 packet蚣录。你可能已經(jīng)注意到割择,該命令包主要就是配置了兩個參數(shù) : CommandSetID 和 VersionComamnd,它們的值均為 1萎河。表明我們想執(zhí)行的命令是屬于命令集合 1 的命令 1荔泳,即 VirtualMachine 的 Version 命令。然后在 performCommand 方法中我們發(fā)送了該命令并收到了 JDWP 的回復(fù)包 reply虐杯。通過解析 reply换可,我們得到了該命令的回復(fù)信息。

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

測試用例的執(zhí)行結(jié)果顯示厦幅,我們通過該命令獲得了 Java 虛擬機的版本信息沾鳄,這正是 VirtualMachine 的 Version 命令的作用。
前面已經(jīng)提到确憨,JDWP 接收到的是調(diào)試器發(fā)送的命令包译荞,返回的就是反饋信息的回復(fù)包。在這個例子中休弃,我們模擬的調(diào)試器會發(fā)送 VirtualMachine 的 Version 命令吞歼。JDWP 在執(zhí)行完該命令后就向調(diào)試器返回 Java 虛擬機的版本信息。
返回信息的包內(nèi)容同樣是在 JDWP Spec 里面規(guī)定的塔猾。比如本例中的回復(fù)包篙骡,Spec 中的描述如下(測試用例中的回復(fù)包解析就是參照這個規(guī)定的 ):

  • 表 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 的命令已經(jīng)有了一個大體的了解淘正。 那么在 JDWP 內(nèi)部是如何處理接收到的命令并返回回復(fù)包的呢?下面以 Apache Harmony 的 JDWP 為例臼闻,為大家介紹其內(nèi)部的實現(xiàn)架構(gòu)鸿吆。

  • 圖 5. JDWP 架構(gòu)圖


    JDWP 架構(gòu)圖
  • 圖 6. JDWP 命令處理流程


    JDWP 命令處理流程

如圖所示,JDWP 接收和發(fā)送的包都會經(jīng)過 TransportManager 進行處理述呐。JDWP 的應(yīng)用層與傳輸層是獨立的惩淳,就在于 TransportManager 調(diào)用的是 JDWP 傳輸接口(Java Debug Wire Protocol Transport Interface),所以無需關(guān)心底層網(wǎng)絡(luò)的具體傳輸實現(xiàn)市埋。TransportManager 的主要作用就是充當(dāng) JDWP 與外界通訊的數(shù)據(jù)包的中轉(zhuǎn)站黎泣,負(fù)責(zé)將 JDWP 的命令包在接收后進行解析或是對回復(fù)包在發(fā)送前進行打包恕刘,從而使 JDWP 能夠?qū)W⒂趹?yīng)用層的實現(xiàn)。

對于收到的命令包抒倚,TransportManager 處理后會轉(zhuǎn)給 PacketDispatcher褐着,進一步封裝后會繼續(xù)轉(zhuǎn)到 CommandDispatcher。然后托呕,CommandDispatcher 會根據(jù)命令中提供的命令組號和命令號創(chuàng)建一個具體的 CommandHandler 來處理 JDWP 命令含蓉。

其中,CommandHandler 才是真正執(zhí)行 JDWP 命令的類项郊。我們會為每個 JDWP 命令都定義一個相對應(yīng)的 CommandHandler 的子類馅扣,當(dāng)接收到某個命令時,就會創(chuàng)建處理該命令的 CommandHandler 的子類的實例來作具體的處理着降。

單線程執(zhí)行的命令

上圖就是一個命令的處理流程圖差油。可以看到任洞,對于一個可以直接在該線程中完成的命令(我們稱為單線程執(zhí)行的命令)蓄喇,一般其內(nèi)部會調(diào)用 JVMTI 方法和 JNI 方法來真正對 Java 虛擬機進行操作。

例如交掏,VirtualMachine 的 Version 命令中妆偏,對于 vmVersion 和 vmName 屬性,我們可以通過 JNI 來調(diào)用 Java 方法 System.getProperty 來獲取盅弛。然后钱骂,JDWP 將回復(fù)包中所需要的結(jié)果封裝到包中后交由 TransportManager 來進行后續(xù)操作。

多線程執(zhí)行的命令

對于一些較為復(fù)雜的命令挪鹏,是無法在 CommandHandler 子類的處理線程中完成的见秽。例如,ClassType 的 InvokeMethod 命令狰住,它會要求在指定的某個線程中執(zhí)行一個靜態(tài)方法张吉。顯然,CommandHandler 子類的當(dāng)前線程并不是所要求的線程催植。這時,JDWP 線程會先把這個請求先放到一個列表中勺择,然后等待创南,直到所要求的線程執(zhí)行完那個靜態(tài)方法后,再把結(jié)果返回給調(diào)試器省核。

JDWP 的事件處理機制

前面介紹的 VirtualMachine 的 Version 命令過程非常簡單稿辙,就是一個查詢和信息返回的過程。在實際調(diào)試過程中气忠,一個 JDI 的命令往往會有數(shù)條這類簡單的查詢命令參與邻储,而且會涉及到很多更為復(fù)雜的命令赋咽。要了解更為復(fù)雜的 JDWP 命令實現(xiàn)機制,就必須介紹 JDWP 的事件處理機制吨娜。

在 Java 虛擬機中脓匿,我們會接觸到許多事件,例如 VM 的初始化宦赠,類的裝載陪毡,異常的發(fā)生,斷點的觸發(fā)等等勾扭。那么這些事件調(diào)試器是如何通過 JDWP 來獲知的呢毡琉?下面,我們通過介紹在調(diào)試過程中斷點的觸發(fā)是如何實現(xiàn)的妙色,來為大家揭示其中的實現(xiàn)機制桅滋。

在這里,我們?nèi)我庹{(diào)試一段 Java 程序身辨,并在某一行中加入斷點丐谋。然后,我們執(zhí)行到該斷點栅表,此時所有 Java 線程都處于 suspend 狀態(tài)笋鄙。這是很常見的斷點觸發(fā)過程。為了記錄在此過程中 JDWP 的行為怪瓶,我們使用了一個開啟了 trace 信息的 JDWP萧落。雖然這并不是一個復(fù)雜的操作,但整個 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)

首先绎晃,調(diào)試器需要發(fā)起一個斷點的請求蜜唾,這是通過 JDWP 的 Set 命令完成的。在 trace 中庶艾,我們看到 AddRequest 就是做了這件事袁余。可以清楚的發(fā)現(xiàn)咱揍,調(diào)試器請求的是一個斷點信息(event=BREAKPOINT[2])颖榜。

在 JDWP 的實現(xiàn)中,這一過程表現(xiàn)為:在 Set 命令中會生成一個具體的 request, JDWP 的 RequestManager 會記錄這個 request(request 中會包含一些過濾條件,當(dāng)事件發(fā)生時 RequestManager 會過濾掉不符合預(yù)先設(shè)定條件的事件)掩完,并通過 JVMTI 的 SetEventNotificationMode 方法使這個事件觸發(fā)生效(否則事件發(fā)生時 Java 虛擬機不會報告)噪漾。

  • 圖 7. JDWP 事件處理流程


    JDWP 事件處理流程

當(dāng)斷點發(fā)生時,Java 虛擬機就會調(diào)用 JDWP 中預(yù)先定義好的處理該事件的回調(diào)函數(shù)且蓬。在 trace 中欣硼,HandleBreakpoint 就是我們在 JDWP 中定義好的處理斷點信息的回調(diào)函數(shù)。它的作用就是要生成一個 JDWP 端所描述的斷點事件來告知調(diào)試器(Java 虛擬機只是觸發(fā)了一個 JVMTI 的消息)缅疟。

由于斷點的事件在調(diào)試器申請時就要求所有 Java 線程在斷點觸發(fā)時被 suspend分别,那這一步由誰來完成呢?這里要談到一個細(xì)節(jié)問題存淫,HandleBreakpoint 作為一個回調(diào)函數(shù)耘斩,其執(zhí)行線程其實就是斷點觸發(fā)的 Java 線程。

顯然桅咆,我們不應(yīng)該由它來負(fù)責(zé) suspend 所有 Java 線程括授。
原因很簡單,我們還有一步工作要做岩饼,就是要把該斷點觸發(fā)信息返回給調(diào)試器荚虚。如果我們先返回信息,然后 suspend 所有 Java 線程籍茧,這就無法保證在調(diào)試器收到信息時所有 Java 線程已經(jīng)被 suspend版述。
反之,先 Suspend 了所有 Java 線程寞冯,誰來負(fù)責(zé)發(fā)送信息給調(diào)試器呢渴析?
為了解決這個問題,我們通過 JDWP 的 EventDispatcher 線程來幫我們 suspend 線程和發(fā)送信息吮龄。實現(xiàn)的過程是俭茧,我們讓觸發(fā)斷點的 Java 線程來 PostEventSet(trace 中可以看到),把生成的 JDWP 事件放到一個隊列中漓帚,然后就開始等待母债。由 EventDispatcher 線程來負(fù)責(zé)從隊列中取出 JDWP 事件,并根據(jù)事件中的設(shè)定尝抖,來 suspend 所要求的 Java 線程并發(fā)送出該事件毡们。

在這里,我們在事件觸發(fā)的 Java 線程和 EventDispatcher 線程之間添加了一個同步機制昧辽,當(dāng)事件發(fā)送出去后漏隐,事件觸發(fā)的 Java 線程會把 JDWP 中的該事件刪除,到這里奴迅,整個 JDWP 事件處理就完成了

結(jié)語

我們在調(diào)試 Java 程序的時候,往往需要對虛擬機內(nèi)部的運行狀態(tài)進行觀察和調(diào)試,JDWP Agent 就充當(dāng)了調(diào)試器與 Java 虛擬機的溝通橋梁取具。它的工作原理簡單來說就是對于 JDWP 命令的處理和事件的管理脖隶。由于 JDWP 在 JPDA 中處于相對底層的位置,調(diào)試器發(fā)出一個 JDI 指令暇检,往往要通過很多 JDWP 命令來完成,

此外由于兩年前在做一個項目产阱,然后發(fā)現(xiàn)生產(chǎn)環(huán)境與本地開發(fā)環(huán)境一致的代碼,運行結(jié)果完全不一樣块仆,然后就通過Eclipse進行Remote遠程調(diào)代碼調(diào)試進行跟蹤构蹬,由此留下比較深刻的印象,看到此文章比較故進行整理悔据,隨便把IDEA和Eclipse的遠程調(diào)試截圖標(biāo)記一下庄敛。
來自文章

Eclipse Remote
IDEA Remote
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市科汗,隨后出現(xiàn)的幾起案子藻烤,更是在濱河造成了極大的恐慌,老刑警劉巖头滔,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怖亭,死亡現(xiàn)場離奇詭異,居然都是意外死亡坤检,警方通過查閱死者的電腦和手機兴猩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來早歇,“玉大人倾芝,你說我怎么就攤上這事∪鼻埃” “怎么了蛀醉?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長衅码。 經(jīng)常有香客問我拯刁,道長,這世上最難降的妖魔是什么逝段? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任垛玻,我火速辦了婚禮,結(jié)果婚禮上奶躯,老公的妹妹穿的比我還像新娘帚桩。我一直安慰自己,他們只是感情好嘹黔,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布账嚎。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪郭蕉。 梳的紋絲不亂的頭發(fā)上疼邀,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音召锈,去河邊找鬼旁振。 笑死,一個胖子當(dāng)著我的面吹牛涨岁,可吹牛的內(nèi)容都是我干的拐袜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼梢薪,長吁一口氣:“原來是場噩夢啊……” “哼蹬铺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沮尿,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤丛塌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后畜疾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赴邻,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年啡捶,在試婚紗的時候發(fā)現(xiàn)自己被綠了姥敛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞎暑,死狀恐怖彤敛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情了赌,我是刑警寧澤墨榄,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站勿她,受9級特大地震影響袄秩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逢并,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一之剧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砍聊,春花似錦背稼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽词疼。三九已至,卻和暖如春疆前,著一層夾襖步出監(jiān)牢的瞬間寒跳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工竹椒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人米辐。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓胸完,卻偏偏與公主長得像,于是被迫代替她去往敵國和親翘贮。 傳聞我的和親對象是個殘疾皇子赊窥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容