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

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

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

開始執(zhí)行
使用cont指令開始執(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來查看本地變量,因?yàn)檫€未執(zhí)行任何一行,所以本地變量中只有三個(gè)方法參數(shù),結(jié)果如下:

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

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

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

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

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

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

使用where 線程ID打印指定線程棧
先使用threads獲取線程ID:

然后使用where 線程ID來打印指定線程棧:

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