Java服務(wù)器調(diào)試指南
在實(shí)際開(kāi)發(fā)中,總會(huì)遇到程序啟動(dòng)不起來(lái)或者運(yùn)行結(jié)果不符合期望的情況,如果是在本地,直接debug就行了,幾乎人人都會(huì),但是如果到了遠(yuǎn)程,大多數(shù)情況下我們可以看日志,通過(guò)日志排查定位到問(wèn)題,但是如果你的日志不多,或者日志中看不出問(wèn)題,此時(shí)情況就比較難以處理了,而實(shí)際上我們?nèi)匀豢梢酝ㄟ^(guò)debug的形式來(lái)解決,只不過(guò)由原來(lái)的本地在ide中通過(guò)GUI來(lái)debug變?yōu)橥ㄟ^(guò)命令行來(lái)debug;
開(kāi)啟服務(wù)debug端口
如果想要對(duì)我們遠(yuǎn)程部署的服務(wù)進(jìn)行debug,那么首先我們要開(kāi)啟debug端口,開(kāi)啟方式如下:
開(kāi)啟遠(yuǎn)程debug:
在Java啟動(dòng)命令后追加系統(tǒng)參數(shù)-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000PS:suspend表示是否需要等待遠(yuǎn)程debug連接再開(kāi)始啟動(dòng),y表示需要等待遠(yuǎn)程debug連接才會(huì)繼續(xù)執(zhí)行;
開(kāi)始debug
當(dāng)我們的服務(wù)啟動(dòng)后,我們就可以開(kāi)始debug了,此時(shí)有兩種情況:
我們本地可以直接連接到服務(wù)所在的機(jī)器;
我們本地?zé)o法直接連接到服務(wù)所在的機(jī)器;(可能是服務(wù)在容器中、堡壘機(jī)后等)
如果我們本地可以直接連接到服務(wù)所在的機(jī)器,那么可以在ide中直接選擇遠(yuǎn)程debug,填寫(xiě)好IP和端口號(hào)即可連接上進(jìn)行遠(yuǎn)程debug,與本地debug的體驗(yàn)基本是一致的,沒(méi)什么好講的,我們這里主要說(shuō)說(shuō)本地?zé)o法連接到服務(wù)所在的機(jī)器時(shí)如何進(jìn)行debug;
當(dāng)我們無(wú)法連接到遠(yuǎn)程服務(wù)所在的機(jī)器時(shí),此時(shí)可以使用jdk中為我們提供的jdb來(lái)調(diào)試,熟悉C語(yǔ)言的可能會(huì)想到gdb,這個(gè)和C語(yǔ)言的gdb是一個(gè)作用,都是用來(lái)讓我們調(diào)試程序的;下面我們來(lái)介紹jdb的詳細(xì)用法:
jdb詳細(xì)用法
在遠(yuǎn)程服務(wù)的主機(jī)上使用jdb命令連接到服務(wù),命令如下(如果jdb所在的機(jī)器與服務(wù)所在機(jī)器不一致,需要將127.0.0.1替換為服務(wù)所在機(jī)器的IP,但是注意需要保證jdb所在的機(jī)器可以通過(guò)這個(gè)ip+端口直連到服務(wù)):
jdb -attach 127.0.0.1:8000如果有源碼,可以使用-sourcepath指定源碼,用法與Java指定classpath一致
jdb -sourcepath /源碼路徑 -attach 127.0.0.1:8000使用上述命令進(jìn)入調(diào)試控制臺(tái)后(命令行),我們就可以使用下面這些命令(常用命令)來(lái)調(diào)試我們的應(yīng)用了:
stop at: 在指定地方斷點(diǎn),例如stop at com.joekerouac.Test:10在com.joekerouac.Test的第10行斷點(diǎn);locals:當(dāng)前堆棧中所有本地變量,包含方法入?yún)ⅲㄗ⒁猓翰话绢?lèi)的成員變量);step:執(zhí)行當(dāng)前行,如果當(dāng)前行調(diào)用了某個(gè)方法,則進(jìn)入方法;step up:執(zhí)行到當(dāng)前方法結(jié)束;next:與step類(lèi)似,不同的是如果當(dāng)前行調(diào)用了某個(gè)方法,會(huì)跳過(guò)方法而不是進(jìn)入方法;cont:執(zhí)行到下個(gè)斷點(diǎn);up:上移線(xiàn)程棧;down:下移線(xiàn)程棧;print: 打印指定變量值;例如一個(gè)方法有一個(gè)參數(shù)叫num,進(jìn)入方法后可以使用print num來(lái)打印num的值;eval:與print類(lèi)似,不同的是這個(gè)支持使用表達(dá)式;where all:打印當(dāng)前所有線(xiàn)程堆棧;where 線(xiàn)程ID:打印指定線(xiàn)程的堆棧,線(xiàn)程ID可以通過(guò)threads獲取;set:修改當(dāng)前某個(gè)變量的值;fields:列出指定類(lèi)的所有字段;list: 查看當(dāng)前所在調(diào)用棧的源碼;
可以在調(diào)試控制臺(tái)使用help查看jdb支持的完整命令列表及其說(shuō)明;
簡(jiǎn)單示例
1、編寫(xiě)一個(gè)Test.java用來(lái)測(cè)試:
public class Test {public static void main(String[] args) {int a = 1;int b = 2;int c = 3;int d = add(a, b, c);int e = add(b, c, d);System.out.println(e);}private static int add(int a, int b, int c) {int d = a + b;return d + c;}}
2、編譯
使用如下命令編譯我們的程序:
# 注意,這里加了一個(gè)-g選項(xiàng),表示編譯時(shí)攜帶debug信息,否則是沒(méi)辦法正常進(jìn)# 行debug的,對(duì)于我們的服務(wù),使用maven編譯時(shí)自動(dòng)開(kāi)啟了該選項(xiàng),攜帶了debug信# 息,所以我們沒(méi)有主動(dòng)指定;javac -g Test.java
3、運(yùn)行我們的程序
使用如下命令運(yùn)行我們的程序:
# 這里我們開(kāi)啟debug選項(xiàng),并且指定監(jiān)聽(tīng)8000端口,這個(gè)端口可以自行修改java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000 Test
運(yùn)行后提示如下:

4、debug連接
再開(kāi)一個(gè)shell窗口,在這個(gè)窗口執(zhí)行如下命令進(jìn)行連接:
# 注意,這里我們運(yùn)行jdb的目錄就是源碼所在目錄,所以源碼目錄使用了當(dāng)前目錄./jdb -sourcepath ./ -attach 127.0.0.1:8000
運(yùn)行結(jié)果如下:

5、開(kāi)始debug
設(shè)置一個(gè)斷點(diǎn)
使用如下命令設(shè)置一個(gè)斷點(diǎn):
# 表示在Test類(lèi)的第7行打一個(gè)斷點(diǎn),注意,類(lèi)名要完全限定名,我們這里由于是測(cè)試,# 所以并沒(méi)有包,也不需要添加包名;stop at Test:7
結(jié)果如下:

開(kāi)始執(zhí)行
使用cont指令開(kāi)始執(zhí)行,結(jié)果如下:

查看源碼
使用list查看當(dāng)前源碼,結(jié)果如下:

可以清晰的看到當(dāng)前執(zhí)行到了我們定義的斷點(diǎn)處停止了;
使用step進(jìn)入方法調(diào)用
使用step命令進(jìn)入add這個(gè)方法調(diào)用的內(nèi)部,執(zhí)行結(jié)果如下:

當(dāng)我們使用list查看當(dāng)前斷點(diǎn)上下的源碼時(shí),結(jié)果如下:

可以看到,當(dāng)前已經(jīng)進(jìn)入方法了(還未執(zhí)行任何行);
使用locals查看本地變量
此時(shí)我們可以使用locals來(lái)查看本地變量,因?yàn)檫€未執(zhí)行任何一行,所以本地變量中只有三個(gè)方法參數(shù),結(jié)果如下:

使用step up來(lái)結(jié)束方法執(zhí)行
使用step up前我們的調(diào)用棧處于這個(gè)狀態(tài):

使用step up后會(huì)直接結(jié)束add方法的執(zhí)行,跳回到方法調(diào)用處,結(jié)果如下:

此時(shí)使用list來(lái)查看,我們回到了主方法中,add方法執(zhí)行完畢,但是還未賦值給d
使用next來(lái)執(zhí)行當(dāng)前行,并且不進(jìn)入方法調(diào)用
此時(shí)我們執(zhí)行next,然后執(zhí)行list,會(huì)發(fā)現(xiàn)我們已經(jīng)來(lái)到了第8行,而此時(shí)如果使用step的話(huà),我們?nèi)詴?huì)進(jìn)入add方法中,而此時(shí)我們不想再看add方法的調(diào)用了,可以使用next來(lái)直接跳過(guò)add方法,來(lái)到下一行,結(jié)果如下

可以看到,當(dāng)我們?cè)诘?行執(zhí)行next的時(shí)候,并沒(méi)有進(jìn)入add方法內(nèi)部,而是直接將其執(zhí)行完畢來(lái)到了第9行;
此時(shí)再使用locals命令查看變量,此時(shí)會(huì)打印出args這個(gè)main方法的入?yún)⒑蚢、b、c、d、e這5個(gè)本地變量,結(jié)果如下

使用set命令修改e的值
此時(shí)我們可以使用set命令來(lái)修改e的值,運(yùn)行結(jié)果如下:

可以發(fā)現(xiàn)此時(shí)e已經(jīng)是12了
打印當(dāng)前線(xiàn)程棧
使用where all打印所有線(xiàn)程棧

使用where 線(xiàn)程ID打印指定線(xiàn)程棧
先使用threads獲取線(xiàn)程ID:

然后使用where 線(xiàn)程ID來(lái)打印指定線(xiàn)程棧:

可以看到,當(dāng)前main線(xiàn)程正處于Test的第9行,而這與我們實(shí)際運(yùn)行的也是一致的
聯(lián)系我
作者微信:JoeKerouac
微信公眾號(hào)(文章會(huì)第一時(shí)間更新到公眾號(hào),如果搜不出來(lái)可能是改名字了,加微信即可=_=|):代碼深度研究院
GitHub:https://github.com/JoeKerouac
