Java遠(yuǎn)程調(diào)試

集群前后臺協(xié)議需要做一些修改,我負(fù)責(zé)jdbc這邊的修改。按照協(xié)議內(nèi)容修改完代碼之后卻面臨一個測試的問題:修改后的后臺又部署在北京,但北京并不是所有的機(jī)器都對天津這邊開放,只給提供一臺機(jī)器A,就是集群的服務(wù)器。
這樣的話,就無法創(chuàng)建節(jié)點(diǎn)的連接,測試沒有辦法進(jìn)行。

一開始用了最簡單的辦法,把打好的jar包通過遠(yuǎn)程ssh放到A上面,再通過ssh去跑用例,打印結(jié)果看看正確與否。但是這樣的效率真的太低了,每做一次修改都要打包、部署、運(yùn)行、分析log。于是借這個機(jī)會學(xué)習(xí)一下java程序的遠(yuǎn)程調(diào)試。以下為總結(jié)。

一些概念

JDPA: java平臺調(diào)試架構(gòu)

JVMTI: JVM端調(diào)試接口

JDI: java端調(diào)試接口

JDWP: java調(diào)試網(wǎng)絡(luò)協(xié)議

image.png

JPDA 定義了一套如何開發(fā)調(diào)試工具的接口和規(guī)范。

JPDA 由三個獨(dú)立的模塊 JVMTI、JDWP、JDI 組成。

調(diào)試者通過 JDI 發(fā)送接受調(diào)試命令。

JDWP 定義調(diào)試者和被調(diào)試者交流數(shù)據(jù)的格式。

JVMTI 可以控制當(dāng)前虛擬機(jī)運(yùn)行狀態(tài)。

上圖中的前端工具就是我們要用到的調(diào)試工具。如JDB、Eclipse等等。這些工具實現(xiàn)了JDI接口,通過這些工具我們可以達(dá)到在命令行或者圖形界面下調(diào)試的目的。

關(guān)于這部分,只是簡單的了解一下概念,更多的關(guān)于JDPA的介紹:JDPA體系

使用JDB進(jìn)行本地調(diào)試

JDB 是jdk自帶的一個調(diào)試工具,用于命令行下調(diào)試java程序

jdb.exe就位于jdk安裝目錄的bin目錄下,安裝好jdk并設(shè)置好環(huán)境變量之后就可以愉快的使用jdb了。

C:\Users\kyu>jdb -help
用法: jdb <options> <class> <arguments>

其中, 選項包括:
    -help             輸出此消息并退出
    -sourcepath <由 ";" 分隔的目錄>
                      要在其中查找源文件的目錄
    -attach <address>
                      使用標(biāo)準(zhǔn)連接器附加到指定地址處正在運(yùn)行的 VM
    -listen <address>
                      等待正在運(yùn)行的 VM 使用標(biāo)準(zhǔn)連接器在指定地址處連接
    -listenany
                      等待正在運(yùn)行的 VM 使用標(biāo)準(zhǔn)連接器在任何可用地址處連接
    -launch
                      立即啟動 VM 而不是等待 'run' 命令
    -listconnectors   列出此 VM 中的可用連接器
    -connect <connector-name>:<name1>=<value1>,...
                      使用所列參數(shù)值通過指定的連接器連接到目標(biāo) VM
    -dbgtrace [flags] 輸出信息供調(diào)試jdb
    -tclient          在 HotSpot(TM) 客戶機(jī)編譯器中運(yùn)行應(yīng)用程序
    -tserver          在 HotSpot(TM) 服務(wù)器編譯器中運(yùn)行應(yīng)用程序

轉(zhuǎn)發(fā)到被調(diào)試進(jìn)程的選項:
    -v -verbose[:class|gc|jni]
                      啟用詳細(xì)模式
    -D<name>=<value>  設(shè)置系統(tǒng)屬性
    -classpath <由 ";" 分隔的目錄>
                      列出要在其中查找類的目錄
    -X<option>        非標(biāo)準(zhǔn)目標(biāo) VM 選項

<class> 是要開始調(diào)試的類的名稱
<arguments> 是傳遞到 <class> 的 main() 方法的參數(shù)

要獲得命令的幫助, 請在jdb提示下鍵入 'help'

上面是啟動JDB的語法說明。

現(xiàn)假設(shè)運(yùn)行程序的工程目錄如下:

JDBTest
  |----bin(編譯生成的class文件)
  |     |----*.class
  |----src(源文件)
  |     |----*.java
  |----lib(依賴的第三方j(luò)ar)
  |     |----*.jar

開始啟動JDB調(diào)試:

Y:\project\JavaProject\JDBTest>jdb -classpath ./bin/;./lib/* -sourcepath ./src/ test.JDBTest

注意,如果有多個文件,windows下使用 ";" 分隔每個文件或目錄,Linux下使用 ":" 分隔每個文件或目錄

-classpath 指定了類路徑,-sourcepath 指定了源文件的路徑

回車,出現(xiàn)如下信息:

Y:\project\JavaProject\JDBTest>jdb -classpath ./bin/;./lib/* -sourcepath ./src/ test.JDBTest
正在初始化jdb...
> 

此時,JDB調(diào)試器等待用戶的輸入,輸入help,出現(xiàn)如下信息:

Y:\project\JavaProject\JDBTest>jdb -classpath ./bin/;./lib/* -sourcepath ./src/ test.JDBTest
正在初始化jdb...
> help
** 命令列表 **
connectors                -- 列出此 VM 中可用的連接器和傳輸

run [class [args]]        -- 開始執(zhí)行應(yīng)用程序的主類

threads [threadgroup]     -- 列出線程
thread <thread id>        -- 設(shè)置默認(rèn)線程
suspend [thread id(s)]    -- 掛起線程 (默認(rèn)值: all)
resume [thread id(s)]     -- 恢復(fù)線程 (默認(rèn)值: all)
where [<thread id> | all] -- 轉(zhuǎn)儲線程的堆棧
wherei [<thread id> | all]-- 轉(zhuǎn)儲線程的堆棧, 以及 pc 信息
up [n frames]             -- 上移線程的堆棧
down [n frames]           -- 下移線程的堆棧
kill <thread id> <expr>   -- 終止具有給定的異常錯誤對象的線程
interrupt <thread id>     -- 中斷線程

print <expr>              -- 輸出表達(dá)式的值
dump <expr>               -- 輸出所有對象信息
eval <expr>               -- 對表達(dá)式求值 (與 print 相同)
set <lvalue> = <expr>     -- 向字段/變量/數(shù)組元素分配新值
locals                    -- 輸出當(dāng)前堆棧幀中的所有本地變量

classes                   -- 列出當(dāng)前已知的類
class <class id>          -- 顯示已命名類的詳細(xì)資料
methods <class id>        -- 列出類的方法
fields <class id>         -- 列出類的字段

threadgroups              -- 列出線程組
threadgroup <name>        -- 設(shè)置當(dāng)前線程組

stop in <class id>.<method>[(argument_type,...)]
                          -- 在方法中設(shè)置斷點(diǎn)
stop at <class id>:<line> -- 在行中設(shè)置斷點(diǎn)
clear <class id>.<method>[(argument_type,...)]
                          -- 清除方法中的斷點(diǎn)
clear <class id>:<line>   -- 清除行中的斷點(diǎn)
clear                     -- 列出斷點(diǎn)
catch [uncaught|caught|all] <class id>|<class pattern>
                          -- 出現(xiàn)指定的異常錯誤時中斷
ignore [uncaught|caught|all] <class id>|<class pattern>
                          -- 對于指定的異常錯誤, 取消 'catch'
watch [access|all] <class id>.<field name>
                          -- 監(jiān)視對字段的訪問/修改
unwatch [access|all] <class id>.<field name>
                          -- 停止監(jiān)視對字段的訪問/修改
trace [go] methods [thread]
                          -- 跟蹤方法進(jìn)入和退出。
                          -- 除非指定 'go', 否則掛起所有線程
trace [go] method exit | exits [thread]
                          -- 跟蹤當(dāng)前方法的退出, 或者所有方法的退出
                          -- 除非指定 'go', 否則掛起所有線程
untrace [methods]         -- 停止跟蹤方法進(jìn)入和/或退出
step                      -- 執(zhí)行當(dāng)前行
step up                   -- 一直執(zhí)行, 直到當(dāng)前方法返回到其調(diào)用方
stepi                     -- 執(zhí)行當(dāng)前指令
next                      -- 步進(jìn)一行 (步過調(diào)用)
cont                      -- 從斷點(diǎn)處繼續(xù)執(zhí)行

list [line number|method] -- 輸出源代碼
use (或 sourcepath) [source file path]
                          -- 顯示或更改源路徑
exclude [<class pattern>, ... | "none"]
                          -- 對于指定的類, 不報告步驟或方法事件
classpath                 -- 從目標(biāo) VM 輸出類路徑信息

monitor <command>         -- 每次程序停止時執(zhí)行命令
monitor                   -- 列出監(jiān)視器
unmonitor <monitor#>      -- 刪除監(jiān)視器
read <filename>           -- 讀取并執(zhí)行命令文件

lock <expr>               -- 輸出對象的鎖信息
threadlocks [thread id]   -- 輸出線程的鎖信息

pop                       -- 通過當(dāng)前幀出棧, 且包含當(dāng)前幀
reenter                   -- 與 pop 相同, 但重新進(jìn)入當(dāng)前幀
redefine <class id> <class file name>
                          -- 重新定義類的代碼

disablegc <expr>          -- 禁止對象的垃圾收集
enablegc <expr>           -- 允許對象的垃圾收集

!!                        -- 重復(fù)執(zhí)行最后一個命令
<n> <command>             -- 將命令重復(fù)執(zhí)行 n 次
# <command>               -- 放棄 (無操作)
help (或 ?)               -- 列出命令
version                   -- 輸出版本信息
exit (或 quit)            -- 退出調(diào)試器

<class id>: 帶有程序包限定符的完整類名
<class pattern>: 帶有前導(dǎo)或尾隨通配符 ('*') 的類名
<thread id>: 'threads' 命令中報告的線程編號
<expr>: Java(TM) 編程語言表達(dá)式。
支持大多數(shù)常見語法。

可以將啟動命令置于 "jdb.ini" 或 ".jdbrc" 中
位于 user.home 或 user.dir 中
>

上面的幫助信息說明了如何進(jìn)行JDB調(diào)試,解釋一下其中的幾個:

step: -- 執(zhí)行當(dāng)前行 相當(dāng)于Eclipse中的F5

step up: -- 一直執(zhí)行, 直到當(dāng)前方法返回到其調(diào)用方 相當(dāng)于Eclipse中的F7

next: -- 步進(jìn)一行 (步過調(diào)用) 相當(dāng)于Eclipse中的F6

cont: -- 從斷點(diǎn)處繼續(xù)執(zhí)行 相當(dāng)于Eclipse中的F8

此時,繼續(xù)輸入:

> stop at test.JDBTest:7
正在延遲斷點(diǎn)test.JDBTest:7。
將在加載類后設(shè)置。
> run
運(yùn)行test.JDBTest
設(shè)置未捕獲的java.lang.Throwable
設(shè)置延遲的未捕獲的java.lang.Throwable
>
VM 已啟動: 設(shè)置延遲的斷點(diǎn)test.JDBTest:7

斷點(diǎn)命中: "線程=main", test.JDBTest.main(), 行=7 bci=0
7               JDBTest jdbTest = new JDBTest();

main[1]

stop at test.JDBTest:7 表示在這個類文件的第7行處打一個斷點(diǎn)

接著,輸入run,就開始進(jìn)入調(diào)試步驟了?,F(xiàn)在可以輸入上面幫助中的語法來了解當(dāng)前程序的執(zhí)行情況了。一試便知

注意, 若想要在調(diào)試時能夠正常輸出調(diào)試信息如變量值等等,需要在編譯java文件時指定 -g 參數(shù),否則無法獲得其運(yùn)行時的調(diào)試信息

另外,使用list可以打印當(dāng)前斷點(diǎn)處的源代碼,如果沒有在啟動JDB時指定源代碼路徑 -sourcepath ./src/ ,那么會提示沒有源代碼信息,無法輸出。此時可以使用命令 use ./src/ 來指定源代碼路徑,再使用list命令時可以正常打印了。

以上就是使用JDB調(diào)試本地程序的方法,具體的使用可根據(jù)實際情況參照語法說明去執(zhí)行。

使用JDB進(jìn)行遠(yuǎn)程調(diào)試

如果程序不是運(yùn)行在本機(jī),而是在其他機(jī)器或者現(xiàn)場的時候,可以使用java提供的遠(yuǎn)程調(diào)試功能。

假設(shè)程序現(xiàn)運(yùn)行在主機(jī) 192.168.101.72 這臺機(jī)器上,該機(jī)器為linux環(huán)境,且只可以通過ssh作為一個普通用戶連接。我們想要在自己的機(jī)器上調(diào)試運(yùn)行在192.168.101.72這臺機(jī)器上的程序。

啟動要調(diào)試的程序

在192.168.101.72這臺主機(jī)上以下面的方式啟動java程序:還是以JDBTest為例

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8899 -classpath ./bin/:./lib/* test.JDBTest

此時,命令行輸出

Listening for transport dt_socket at address: 8899

并處于等待狀態(tài)

下面是幾個參數(shù)的解釋:

-Xdebug 啟用調(diào)試特性。

-Xrunjdwp:<sub-options> 在目標(biāo) VM 中加載 JDWP 實現(xiàn)。它通過傳輸和 JDWP 協(xié)議與獨(dú)立的調(diào)試器應(yīng)用程序通信。下面介紹一些特定的子選項。
從 Java V5 開始,您可以使用 -agentlib:jdwp 選項,而不是 -Xdebug 和 -Xrunjdwp。但如果連接到 V5 以前的 VM,只能選擇 -Xdebug 和 -Xrunjdwp。下面簡單描述 -Xrunjdwp 子選項。

transport 這里通常使用套接字傳輸。但是在 Windows 平臺上也可以使用共享內(nèi)存?zhèn)鬏敗?/p>

server 如果值為 y,目標(biāo)應(yīng)用程序監(jiān)聽將要連接的調(diào)試器應(yīng)用程序。否則,它將連接到特定地址上的調(diào)試器應(yīng)用程序。

address 這是連接的傳輸?shù)刂?。如果服?wù)器為 n,將嘗試連接到該地址上的調(diào)試器應(yīng)用程序。否則,將在這個端口監(jiān)聽連接。

suspend 如果值為 y,目標(biāo) VM 將暫停,直到調(diào)試器應(yīng)用程序進(jìn)行連接。

本機(jī)連接遠(yuǎn)程程序并啟動調(diào)試

在本機(jī)上命令行下輸入:

jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.101.72,port=8899

然后就進(jìn)入了調(diào)試界面,你可以像調(diào)試本機(jī)程序那樣使用JDB的一些命令來調(diào)試了。當(dāng)退出調(diào)試程序時,遠(yuǎn)程主機(jī)上的程序也就退出了。

使用Eclipse進(jìn)行遠(yuǎn)程調(diào)試

可以使用Eclipse進(jìn)行遠(yuǎn)程調(diào)試,就上上面使用JDB一樣。

啟動要調(diào)試的程序

與JDB遠(yuǎn)程調(diào)試一樣,啟動遠(yuǎn)程主機(jī)上的程序:

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8899 -classpath ./bin/:./lib/* test.JDBTest

本機(jī)啟動Eclipse進(jìn)行調(diào)試

首先要右鍵工程->java compiler

image.png

上圖中的幾個選項最好全部打勾,否則調(diào)試時會出現(xiàn)無法打斷點(diǎn)或者獲取不到行號等問題(關(guān)于這幾個選項的含義在之前的總結(jié)中有提到)

接著,右鍵工程->Debug As->Run Configurations, 在出現(xiàn)的對話框中選擇Remote Java Application, 右鍵->New, 出現(xiàn)如下界面:

image.png

在Connect頁中,選擇對應(yīng)的java 工程,Connection Type選擇 Socket Attach,然后填寫遠(yuǎn)程主機(jī)的ip和端口,這里應(yīng)該填寫192.168.101.72和8899。
在Source頁中可以添加源代碼,如用到的第三方j(luò)ar的源代碼或者引用的工程,調(diào)試時就可以進(jìn)入到這部分代碼查看。在Common頁可以設(shè)置編碼的配置。

接下來點(diǎn)擊Debug按鈕,就可以愉快的在本機(jī)調(diào)試遠(yuǎn)程程序了,就像調(diào)試本地程序那樣。只不過可能有一點(diǎn)一點(diǎn)慢,不過比打Log的方式要好很多了。

參考

使用 Eclipse 遠(yuǎn)程調(diào)試 Java 應(yīng)用程序

JDB的簡單使用

深入 Java 調(diào)試體系: 第 1 部分,JPDA 體系概覽

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容