使用JDB直接在遠(yuǎn)程服務(wù)器上進(jìn)行調(diào)試

程序開發(fā)過(guò)程中许赃,debug 是必不可少的一部分跟压,它能幫助我們及時(shí)發(fā)現(xiàn)一些不易察覺的 bug,但并不是所有的 bug 都能有幸在開發(fā)過(guò)程中就被發(fā)現(xiàn)塞茅,當(dāng)程序被部署到遠(yuǎn)程服務(wù)器后亩码,bug 的排查可能就不那么輕松了。

開發(fā)過(guò)程中常常會(huì)發(fā)現(xiàn)程序在我們本地運(yùn)行的時(shí)候一切正常野瘦,但在測(cè)試環(huán)境或生產(chǎn)環(huán)境會(huì)出現(xiàn)不可預(yù)測(cè)的問(wèn)題描沟,也就是一些潛在的 bug 在特定的環(huán)境下才會(huì)暴露出來(lái)飒泻,可能是數(shù)據(jù)引起的,也可能是其他不確定的因素吏廉。此時(shí)通常的方法可能就是通過(guò)打印出更加詳細(xì)的日志再進(jìn)行分析泞遗,而日志的詳細(xì)粒度也往往不容易把控,過(guò)多會(huì)增加分析的復(fù)雜度席覆,過(guò)少又不易于發(fā)現(xiàn)問(wèn)題史辙。總之沒有在本地 debug 來(lái)的痛快佩伤。有同學(xué)可能會(huì)說(shuō)聊倔,”我們可以讓遠(yuǎn)程 JVM 在啟動(dòng)的時(shí)候加載 JDWP Agent,然后在本地 IDE 中指定端口進(jìn)行遠(yuǎn)程連接生巡,從而進(jìn)行調(diào)試“耙蔑。不可否認(rèn),這是最理想的方案障斋,但現(xiàn)實(shí)往往有點(diǎn)骨感纵潦,起碼在我們公司,這個(gè)過(guò)程會(huì)讓人崩潰垃环。我們所有的服務(wù)器都部署在北美邀层,由于網(wǎng)絡(luò)原因,讓 IDE 與遠(yuǎn)程 JVM 建立連接就需要消耗一點(diǎn)時(shí)間遂庄,之后服務(wù)器上的程序可能早就已經(jīng)跑到斷點(diǎn)了寥院,而本地 IDE 還沒有及時(shí)反應(yīng)過(guò)來(lái)。這種卡頓涛目、延遲現(xiàn)象讓調(diào)試過(guò)程來(lái)的相當(dāng)痛苦秸谢,還不如直接去分析日志。

換個(gè)思路:

“難道我們不能直接在遠(yuǎn)程服務(wù)器上直接進(jìn)行調(diào)試嗎霹肝?”

有同學(xué)可能會(huì)說(shuō):

“服務(wù)器通常是沒有桌面的估蹄,如何在上面使用 IDE?”

這其實(shí)還是思維的固化沫换,IDE 提供的 Debugger(調(diào)試器)其實(shí)就是 Java Debug Interface(JDI)的一個(gè)實(shí)現(xiàn)臭蚁,比如我們?cè)偈煜げ贿^(guò)的Eclipse,它的兩個(gè)插件org.eclipse.jdt.debug.uiorg.eclipse.jdt.debug讯赏,前者是Debugger的界面實(shí)現(xiàn)垮兑,后者就是JDI的一個(gè)完整實(shí)現(xiàn)。而 JDK 自帶的jdb也是 JDI 的一個(gè)實(shí)現(xiàn)漱挎,所以我們完全可以直接使用這個(gè)自帶工具進(jìn)行調(diào)試系枪。

Java Debugger(JDB)是一個(gè)用來(lái)調(diào)試Java類文件的命令行工具,它跟 Eclipse磕谅、Intellij 等 IDE 里的調(diào)試器一樣私爷,都是 Java Platform Debugger Architecture - JPDA 三大模塊中最高層模塊 JDI 的完整實(shí)現(xiàn)雾棺。

JPDA - Java 調(diào)試體系

說(shuō)到這里,我們有必要先簡(jiǎn)單了解一下 JPDA当犯。JPDA 由三個(gè)相對(duì)獨(dú)立的模塊組成垢村,由低到高分別是 JVM 工具接口(JVMTI)、Java 調(diào)試線協(xié)議(JDWP)嚎卫、Java 調(diào)試接口(JDI)嘉栓,層次結(jié)構(gòu)如下圖:

JPDA - Java調(diào)試體系

1. JVMTI

處于整個(gè) JPDA 體系的最底層的 Java 虛擬機(jī)工具接口,是一套由虛擬機(jī)直接提供的 native 接口拓诸,由 C 語(yǔ)言實(shí)現(xiàn)侵佃,所有調(diào)試功能本質(zhì)上都需要通過(guò) JVMTI 來(lái)提供。通過(guò)這些接口奠支,開發(fā)人員不僅調(diào)試在該虛擬機(jī)上運(yùn)行的 Java 程序馋辈,還能查看它們運(yùn)行的狀態(tài),設(shè)置回調(diào)函數(shù)倍谜,控制某些環(huán)境變量迈螟,從而優(yōu)化程序性能。

2. JDWP

一個(gè)通訊交互協(xié)議尔崔,定義了調(diào)試器與目標(biāo)虛擬機(jī)之間傳遞的信息的格式答毫,包括請(qǐng)求命令、回應(yīng)數(shù)據(jù)和錯(cuò)誤代碼季春。同樣也是由 C 語(yǔ)言實(shí)現(xiàn)洗搂。

3. JDI

三個(gè)模塊中最高層的接口,在多數(shù)的 JDK 中载弄,它是由 Java 語(yǔ)言實(shí)現(xiàn)的耘拇。 通過(guò)它,調(diào)試工具開發(fā)人員就能通過(guò)調(diào)試器來(lái)遠(yuǎn)程操控目標(biāo)虛擬機(jī)上被調(diào)試程序的運(yùn)行宇攻。

Java Debugger(JDB)

下面我們來(lái)了解一下這個(gè) JDK 自帶工具的使用方法惫叛。JDB 提供了多種連接目標(biāo)程序 JVM 的方式,這里介紹最常用的兩種逞刷。

1. 由 jdb 命令創(chuàng)建目標(biāo) JVM

這種方式下挣棕,jdb 命令直接為目標(biāo) Java 程序啟動(dòng)一個(gè) JVM,加載類信息亲桥,即程序的啟動(dòng)是由 jdb 命令直接觸發(fā)的,啟動(dòng)成功后固耘,目標(biāo) JVM 就會(huì)被暫停题篷,等待用戶輸入命令來(lái)讓程序得以執(zhí)行。這就是我們?cè)诒镜赜?IDE 進(jìn)行 debug 的方式厅目。
比如用 JDB 調(diào)試如下程序:

// Test.java

package demo;

public class Test {
    private int base = 1;
  
    public int add(int a) {
        return base + a;
    }
}
// Main.java

package demo;

public class Main {

    public static void main(String[] args) {
        Test t = new Test();
        int result = t.add(2);
        System.out.println(result);
    }

}

編譯上面兩個(gè)源文件后番枚,在控制臺(tái)進(jìn)行調(diào)試:

  • 通過(guò) jdb <主類的全路徑名> 啟動(dòng) JVM法严,這個(gè)示例當(dāng)中就是 jdb demo.Main
    dereck-mbp:temp Dereck$ jdb demo.Main
    正在初始化jdb...
    > 
    
  • 設(shè)置斷點(diǎn),兩種方式
    > stop ?
    用法: stop at <class>:<line_number> 或
         stop in <class>.<method_name>[(argument_type,...)]
    
    對(duì)于本例葫笼,我們通過(guò)方法名的方式給 add() 加斷點(diǎn)深啤,執(zhí)行命令如下,斷點(diǎn)會(huì)設(shè)置在方法的第一行
    > stop in demo.Test.add
    正在延遲斷點(diǎn)demo.Test.add路星。
    將在加載類后設(shè)置溯街。
    > 
    
  • 通過(guò) run 命令運(yùn)行程序,它會(huì)自動(dòng)從 main() 執(zhí)行洋丐,一直到斷點(diǎn)處暫停呈昔,等待用戶輸入后續(xù)命令。run 命令只適用于由 jdb 直接創(chuàng)建啟動(dòng) JVM 的方式
    > run
    運(yùn)行demo.Main
    設(shè)置未捕獲的java.lang.Throwable
    設(shè)置延遲的未捕獲的java.lang.Throwable
    > 
    VM 已啟動(dòng): 設(shè)置延遲的斷點(diǎn)demo.Test.add
    
    斷點(diǎn)命中: "線程=main", demo.Test.add(), 行=8 bci=0
    8            return base + a;
    
    main[1] 
    
  • 通過(guò) printdump 命令查看此時(shí)指定變量的值友绝,print 用于查看簡(jiǎn)單類型堤尾,dump 用于查看對(duì)象類型
    main[1] print a
     a = 2
    main[1] print this
     this = "demo.Test@41975e01"
    main[1] dump this
     this = {
        base: 1
    }
    main[1] 
    
    如果執(zhí)行 print 命令查看方法參數(shù) a 報(bào)如下錯(cuò)誤(“未知變量名
    a”)的話,需要在編譯的時(shí)候迁客,給 javac 加一個(gè)參數(shù) -g郭宝, 比如 javac -g demo/Test.java
    main[1] print a
    com.sun.tools.example.debug.expr.ParseException: Name unknown: a
     a = 空值
    main[1] 
    
  • 通過(guò) stepnextcont 命令繼續(xù)執(zhí)行程序
    • step 命令相當(dāng)于 Eclipse 當(dāng)中的 F5掷漱,如果當(dāng)前語(yǔ)句是另一個(gè)方法調(diào)用時(shí)粘室,會(huì)進(jìn)入那個(gè)方法當(dāng)中
    • next 命令相當(dāng)于 F6,只會(huì)逐行執(zhí)行切威,不會(huì)進(jìn)入被調(diào)用的其它方法
    • cont 命令相當(dāng)于 F8育特,從當(dāng)前行一直執(zhí)行到下一個(gè)斷點(diǎn),如果沒有就一直執(zhí)行到程序結(jié)束
    main[1] cont
    > 3
    
    應(yīng)用程序已退出
    dereck-mbp:temp Dereck$ 
    

更多 JDB 命令請(qǐng)參考 Oracle 官方文檔

2. 由 jdb 命令 attach 到已經(jīng)處于運(yùn)行狀態(tài)的目標(biāo) JVM

這種方式適用于遠(yuǎn)程調(diào)試先朦,也是我們直接在遠(yuǎn)程服務(wù)器上進(jìn)行 debug 的方式缰冤。 它需要目標(biāo) JVM 自身在啟動(dòng)的時(shí)候傳入一些額外的參數(shù),大致格式如下:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=<PORT> <主類的全路徑名> 

其中 address 參數(shù)可選喳魏,如果不指定的話會(huì)隨機(jī)分配一個(gè)可用端口棉浸。
為了演示,這里用 Spring Initializr 創(chuàng)建了一個(gè)簡(jiǎn)單的 Web Application : Helloworld刺彩,除了生成的代碼迷郑,新建了一個(gè)簡(jiǎn)單的 HelloworldController.java

package com.example.helloworld.helloworld;

import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;


@Controller
public class HelloworldController {

    private final String message = "helloworld";

    @RequestMapping("/")
    @ResponseBody
    String home() {
        return message;
    }
  
}
  • 通過(guò) Maven 構(gòu)建打包成 jar 文件
    mvn package
    
  • 打包成功后,啟動(dòng) WEB 應(yīng)用
    java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n -jar helloworld-0.0.1-SNAPSHOT.jar
    
  • 啟動(dòng)成功后创倔,日志中會(huì)打印出提供給 jdb 連接的端口嗡害,因?yàn)槲覀冎拔粗付ǎ赃@里隨機(jī)分配了一個(gè)
    dereck-mbp:target Dereck$ java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n -jar helloworld-0.0.1-SNAPSHOT.jar 
    Listening for transport dt_socket at address: 51750
    
  • 通過(guò) jdb 命令連接到正在運(yùn)行的 JVM
    dereck-mbp:~ Dereck$ jdb -connect com.sun.jdi.SocketAttach:port=51750
    設(shè)置未捕獲的java.lang.Throwable
    設(shè)置延遲的未捕獲的java.lang.Throwable
    正在初始化jdb...
    > 
    
  • 后續(xù)設(shè)置斷點(diǎn)及輸入命令進(jìn)行調(diào)試的步驟與方式1一樣畦攘,這里就不在累贅了

這種方式?jīng)]有因?yàn)榫W(wǎng)絡(luò)原因而導(dǎo)致的卡頓霸妹、延遲現(xiàn)象,但操作起來(lái)可能比較復(fù)雜知押,不是很直觀叹螟,但對(duì)于在不改變系統(tǒng)運(yùn)行環(huán)境鹃骂、又沒有詳細(xì) log 的情況下快速進(jìn)行問(wèn)題的排查還是有一定的幫助的。不過(guò)罢绽,不要在生產(chǎn)環(huán)境使用畏线,因?yàn)橐坏┻M(jìn)入斷點(diǎn),程序就會(huì)被中斷了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末良价,一起剝皮案震驚了整個(gè)濱河市寝殴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棚壁,老刑警劉巖杯矩,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異袖外,居然都是意外死亡史隆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門曼验,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)泌射,“玉大人,你說(shuō)我怎么就攤上這事鬓照∪劭幔” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵豺裆,是天一觀的道長(zhǎng)拒秘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)臭猜,這世上最難降的妖魔是什么躺酒? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蔑歌,結(jié)果婚禮上羹应,老公的妹妹穿的比我還像新娘。我一直安慰自己次屠,他們只是感情好园匹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著劫灶,像睡著了一般裸违。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上本昏,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天供汛,我揣著相機(jī)與錄音,去河邊找鬼。 笑死紊馏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蒲犬。 我是一名探鬼主播朱监,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼原叮!你這毒婦竟也來(lái)了赫编?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奋隶,失蹤者是張志新(化名)和其女友劉穎擂送,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唯欣,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嘹吨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了境氢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蟀拷。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖萍聊,靈堂內(nèi)的尸體忽然破棺而出问芬,到底是詐尸還是另有隱情,我是刑警寧澤寿桨,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布此衅,位于F島的核電站,受9級(jí)特大地震影響亭螟,放射性物質(zhì)發(fā)生泄漏挡鞍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一媒佣、第九天 我趴在偏房一處隱蔽的房頂上張望匕累。 院中可真熱鬧,春花似錦默伍、人聲如沸欢嘿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)炼蹦。三九已至,卻和暖如春狸剃,著一層夾襖步出監(jiān)牢的瞬間掐隐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虑省,地道東北人匿刮。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像探颈,于是被迫代替她去往敵國(guó)和親熟丸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,509評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理伪节,服務(wù)發(fā)現(xiàn)光羞,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 集群前后臺(tái)協(xié)議需要做一些修改怀大,我負(fù)責(zé)jdbc這邊的修改纱兑。按照協(xié)議內(nèi)容修改完代碼之后卻面臨一個(gè)測(cè)試的問(wèn)題:修改后的后...
    德彪閱讀 3,666評(píng)論 0 2
  • 每次出去觀看一部影片,就給做賊似的化借,不想讓孩子和婆婆媽知道潜慎,因?yàn)槌鋈タ措娪安粠Ш⒆樱易约壕陀X得有愧屏鳍】贝浚可他提前就訂...
    任小藝閱讀 1,560評(píng)論 1 3
  • 李特這特題目有點(diǎn)蛋疼,因?yàn)槟壳爸唤邮芤环N結(jié)果钓瞭。我做的恰好和它要的結(jié)果不一樣驳遵,但是我覺得我這種走法走出來(lái)也是沒錯(cuò)的。...
    土汪閱讀 480評(píng)論 0 1