Debug功能是我們平時(shí)學(xué)習(xí)或工作中,最常用的IDEA功能之一??捎脕碜粉櫞a流程,確認(rèn)運(yùn)行過程中參數(shù)變化,分析定位程序的錯(cuò)誤,線上問題追蹤,也可以用以學(xué)習(xí)第三方框架等。程序從一個(gè)黑盒,開啟Debug功能后,就變的一絲不掛了,我們能明白每一步發(fā)生了什么,以及為什么發(fā)生。
看到這里你也許會(huì)說:“Debug有什么難的,點(diǎn)開Debug,一直下一步,梭哈到底不就完了嗎,費(fèi)那事干嘛?”那如果,你想回退怎么辦,或你想更改參數(shù)值怎么辦,重新發(fā)一次請求么?再或者需要遠(yuǎn)程調(diào)試時(shí),你咋辦,這些都是我們接下來要全面學(xué)習(xí)的內(nèi)容。如果能熟練運(yùn)用Debug功能的話,一定可以事半功倍。
注:開發(fā)工程中,用Debug代替Run,是個(gè)不錯(cuò)的習(xí)慣,可以讓我們隨時(shí)調(diào)試代碼
接下來我會(huì)從這幾個(gè)方面全面向大家介紹一下IDEA的Debug功能。
- 準(zhǔn)備工作
- 主界面介紹
- 各功能鍵詳解
- 查看變量
- 更改變量值
- 設(shè)置斷點(diǎn)條件
- 計(jì)算表達(dá)式
- 回退操作
- 強(qiáng)制返回
- 多線程調(diào)試
- Stream 調(diào)試
- 遠(yuǎn)程Debug
- 最后
準(zhǔn)備工作
開始講解Debug功能之前,我們需要準(zhǔn)備以下工具:
- IDEA
- 調(diào)試代碼
調(diào)試代碼如下,我們之后的操作,大部分在此代碼上進(jìn)行。此為Leetcode上的經(jīng)典題目:兩數(shù)之和 的解法。
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Created By L
* Description:
*/
public class ToSum {
public static int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int sub = target - nums[i];
// 查找余數(shù)是否在表中,有則返回兩個(gè)數(shù)的索引
// 沒有則將其信息添加進(jìn)哈希表
if (map.containsKey(sub)) {
return new int[]{map.get(sub), i};
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
public static void main(String[] args) {
int[] nums = {2, 7, 11, 15};
int target = 9;
int[] ints = twoSum(nums, target);
System.out.println(Arrays.toString(ints));
}
}
Debug窗口
有同學(xué)的Debug窗口沒有在IDEA底部顯示,可通過雙擊Shift鍵方式打開搜索功能框,然后輸入Debug,選擇如圖第二個(gè)選項(xiàng),Debug窗口即會(huì)出現(xiàn)在底部。

當(dāng)然,右鍵Debug Toolbar 我們還可以選擇,讓窗口出現(xiàn)在上下左右任意位置。

注:這里用的mac,快捷鍵和Windows不太一樣,不過不影響大家學(xué)習(xí)技能,我用到的快捷鍵一定是兩平臺通用的,然后這里正好強(qiáng)迫下大家去實(shí)際操作一下,學(xué)過沒用過,等于沒學(xué)過。
主界面介紹
我們先來看下IDEA中Debug模式下的主界面。
- Debug: 啟動(dòng)Debug功能。
- 斷點(diǎn): 可在左邊欄單擊,以打上斷點(diǎn),以Debug方式運(yùn)行到此時(shí),程序會(huì)停下。
- 服務(wù)按鈕: 可以在這里關(guān)閉服務(wù)、啟動(dòng)服務(wù)、修改Debug配置,設(shè)置斷點(diǎn)等。
- 調(diào)試按鈕: 共8個(gè)其中包括:Show Execution Point、Step Over (F8)、Step Into (F7)、Force Step Into、Step Out、Drop Frame、Run to Cursor、Evaluate Expression,后面會(huì)詳細(xì)解釋功能。
- Frames: 顯示了該線程調(diào)試所經(jīng)過的所有方法,勾選右上角的漏斗(Show All Frames)按鈕,就不會(huì)顯示其它類庫的方法了,否則這里會(huì)有一大堆的方法。
- Variables: 可以查看變量具體的值,當(dāng)然也可以修改。
- Watches: 查看變量,可以將Variables區(qū)中的變量拖到Watches中查看,默認(rèn)的Debug窗口,可能并沒有這個(gè)窗口,你需要點(diǎn)擊Variables窗口的“眼鏡”??圖標(biāo),此窗口才會(huì)出現(xiàn)。再點(diǎn)擊Watches列里“眼鏡”??圖標(biāo),即可收回。
-
Debug窗口: 可以設(shè)置Debug窗口是否固定,或顯示在哪個(gè)邊欄,只需右鍵即可選擇。你甚至可以拖動(dòng)該Debug窗口,使其以獨(dú)立窗口形式存在。
各功能鍵詳解
接下來,我們依次講講服務(wù)按鈕、調(diào)試按鈕、Watches操作欄的作用:

服務(wù)按鈕
服務(wù)按鈕如上圖中1區(qū)所示,從上至下,共10個(gè)按鈕,IDEA版本不同,可能有小部分不一致。
- Rerun ‘xxx’: 重新運(yùn)行程序,點(diǎn)擊會(huì)以Debug方式重啟服務(wù)。
- Edit Run Configuration:“xxx” : 更改運(yùn)行時(shí)配置,其中包括運(yùn)行Java版本更,環(huán)境變量等更改,還可以用于進(jìn)行遠(yuǎn)程調(diào)試,之后詳解。
- Resume Program (F9): 恢復(fù)程序,從一個(gè)斷點(diǎn)運(yùn)行至下一個(gè)斷點(diǎn),沒有斷點(diǎn)則運(yùn)行完整個(gè)程序。
- Pause Program: 暫停程序,當(dāng)我們執(zhí)行某步時(shí)間太長,或遇到死循環(huán)時(shí),可使用其暫停程序,如想繼續(xù)執(zhí)行,再點(diǎn)擊Resume Program (F9)即可。
- Stop ‘xxx’ (Ctrl + F2): 終止當(dāng)前Debug程序。
- View Breakpoints: 查看所有斷點(diǎn),還可選擇按包、文件或class類進(jìn)行斷點(diǎn)分組,還可對斷點(diǎn)進(jìn)行一些配置,包括條件配置,多線程配置等,后面會(huì)詳解。
- Mute Breakpoints: 沉默所有斷點(diǎn),選擇這個(gè)后,所有斷點(diǎn)變?yōu)榛疑?,斷點(diǎn)失效。再次點(diǎn)擊,斷點(diǎn)變?yōu)榧t色,有效。如果只想使某一個(gè)斷點(diǎn)失效,可以在斷點(diǎn)上右鍵取消Enabled,會(huì)顯示空心的紅圈,表示當(dāng)前斷點(diǎn)不可用。
- Get Thread Dump: 導(dǎo)出當(dāng)前堆棧信息,可用作其它分析。
- Settings: 一些顯示設(shè)置,可以設(shè)置是否在代碼界面顯示Values、Return Values等,開啟即可。
- Pin Tab: 是否固定當(dāng)前Debug標(biāo)簽頁。
調(diào)試按鈕
調(diào)試按鈕如上圖中2區(qū)所示,從左至右,共9個(gè)按鈕,也是我們平時(shí)除服務(wù)按鈕外用的最多的操作了。
- Show Execution Point : 定位,點(diǎn)擊此按鈕可定位到當(dāng)前代碼執(zhí)行的行。
- Step Over (F8): 步過,一行一行地往下走,如果執(zhí)行到子方法,子方法內(nèi)又無斷點(diǎn)的情況下,會(huì)跳過子方法繼續(xù)執(zhí)行。
- Step Into (F7): 步入,一行一行地往下走,如果執(zhí)行到子方法,無論子方法有無斷點(diǎn),都會(huì)進(jìn)入到子方法,子方法執(zhí)行完后返回,繼續(xù)執(zhí)行。
- Force Step Into (Alt + Shift + F7): 強(qiáng)制步入,能進(jìn)入任何方法,查看底層源碼的時(shí)候可以用這個(gè)進(jìn)入官方類庫的方法。
- Step Out (Shift + F8): 步出,從步入的方法內(nèi)退出到方法調(diào)用處,此時(shí)方法已執(zhí)行完畢,只是還沒有完成賦值。
- Drop Frame: 回退斷點(diǎn),相當(dāng)于后悔藥,后面會(huì)詳解。
- Run to Cursor: 使程序運(yùn)行至光標(biāo)處,而光標(biāo)處不需要打斷點(diǎn),運(yùn)行過程中,有斷點(diǎn)會(huì)在斷點(diǎn)處停止。
- Evaluate Expression…: 計(jì)算表達(dá)式,后面章節(jié)詳細(xì)說明。
- Trace Current Stream Chain: 顯示Stream處理的整體過程。Lambda調(diào)試神器,之后會(huì)詳解。
Watches操作欄
Watches窗口是觀察變量變化的窗口,其操作欄如上圖中3區(qū)所示,共6個(gè)按鈕。
- new Watch…: 新增觀察變量。
- Remove Watch: 刪除觀察變量
- Move Watch Up: 向上移動(dòng)觀察變量。
- Move Watch Down: 向下移動(dòng)觀察變量。
- Duplicate Watch: 復(fù)制觀察變量。
- Show watches in variables tab: Watches不會(huì)默認(rèn)打開為新窗口,點(diǎn)擊“眼鏡”??圖標(biāo)后,才會(huì)打開為新窗口,再次點(diǎn)擊會(huì)回到Variables邊欄。
查看變量
查看變量值的方式有四種,這里我們首先確定我們的服務(wù)按鈕中的Setting中有選擇在代碼界面顯示Values、Return Values等。如下圖所示即可,沒找到也沒關(guān)系,默認(rèn)都會(huì)顯示的。

在代碼行的后面,有灰色的變量值顯示。

光標(biāo)懸停到參數(shù)上,也可顯示當(dāng)前變量信息。如果是復(fù)雜的變量,還可以點(diǎn)開查看詳細(xì)變量值


也可在Variables里查看變量值,甚至是操作變量值,這里也是變量操作最多的地方。比如,我們運(yùn)行到一半,想更改某一個(gè)變量的值怎么辦?重新發(fā)一次請求嗎,還是重新運(yùn)行一次?其實(shí)不用這么麻煩,直接更改值即可!選擇 Set Value… 即可

按下回車,當(dāng)前target變量的值就改變了,而后可以繼續(xù)運(yùn)行。

在Watches也可以觀察變量值,我們可以通過從Variables里拖變量到Watches中觀察,也可以通過點(diǎn)擊 new Watch… 添加觀察變量。
這里和Variables的區(qū)別是:Variables窗口只會(huì)顯示當(dāng)前代碼行能看到的變量,Watches只要添加了該變量,則會(huì)一直存在Watches窗口。

更改變量值
其實(shí)上一節(jié),已經(jīng)講了如何在Variables窗口里更改變量值,這里再講一種如何更改變量值,如下圖所示,這里還可以把變量加入Watches窗口中。

比如這里我們這把num[0]改成了22

設(shè)置斷點(diǎn)條件
想像一種情況,我們要遍歷一個(gè)大集合,我們只有當(dāng)集合為null時(shí),才停下來,那我們難道要一次一次的點(diǎn)過去嗎,點(diǎn)到900次的時(shí)候,你一時(shí)手快,跳過了怎么辦?其實(shí)我們可以設(shè)置斷點(diǎn)停留的條件,當(dāng)條件滿足的時(shí)候才停下,否則繼續(xù)運(yùn)行。以下方代碼為例:

我們想在值為null值時(shí),停下斷點(diǎn),看看是哪里為null,然后去更改數(shù)據(jù)庫,進(jìn)行如下設(shè)置即可。

只有當(dāng) n == null 這個(gè)條件為空時(shí),這個(gè)斷點(diǎn)才會(huì)停止!
計(jì)算表達(dá)式
我們在Debug時(shí),想知道運(yùn)行時(shí)某個(gè)方法的調(diào)用值或表達(dá)式的結(jié)果值,但是這個(gè)方法又沒寫在代碼中,我們總是會(huì)添加上該方法代碼,然后重新運(yùn)行一次對吧。
其實(shí)不用重新運(yùn)行也可以計(jì)算其值,點(diǎn)擊Evaluate Expression…按鍵即可。比如,我想知道此時(shí)map.keySet()的值會(huì)是多少,直接點(diǎn)擊Evaluate即可。

當(dāng)然我們也可以計(jì)算一些復(fù)雜的表達(dá)式

這里的Evaluate功能其實(shí)就是:用當(dāng)前代碼的上下文環(huán)境,寫出代碼表達(dá)式,最后計(jì)算出臨時(shí)的結(jié)果值??煞奖阄覀兊母鞣N假設(shè)性代碼實(shí)現(xiàn),而不用每次都去重啟代碼,也不會(huì)影響原代碼的正常運(yùn)行。
回退操作
回退操作即是Debug中的后悔藥,IDEA中只能回退到上一個(gè)方法調(diào)用處,并不能一步一步回退或是回退到上一個(gè)斷點(diǎn)處。還有一點(diǎn)值得注意的是,回退只是重新走一下流程,之前的某些參數(shù)/數(shù)據(jù)的狀態(tài)已經(jīng)改變了的,是無法回退到之前的狀態(tài)的,如對象、集合、更新了數(shù)據(jù)庫數(shù)據(jù)等等。
比如,main方法調(diào)用A方法,A方法里刪除了數(shù)據(jù)庫里的一行數(shù)據(jù),我們也執(zhí)行了,這時(shí)我們回退,會(huì)回退到main方法中調(diào)用A方法處,我們A方法中的局部變量的確是回退了,但是數(shù)據(jù)庫里的值已經(jīng)被刪了,回退不了了,其它第三方對象、集合原理也類似,回退不了?;赝说姆绞胶芎唵?,在Frames窗口中,點(diǎn)擊Drop Frame按鍵即可。

也可一次回退幾個(gè)方法,比如我這里是main調(diào)用a方法,a調(diào)用b方法,b調(diào)用c方法,我這里直接選中a方法Frame,右鍵點(diǎn)擊Drop Frame,這樣就直接回退到main方法了,右鍵的Drop Frame和操作欄上的效果是一致的。

強(qiáng)制返回
想象一個(gè)場景,當(dāng)前bug已經(jīng)找到了,你不想再繼續(xù)執(zhí)行了,再執(zhí)行后面的代碼會(huì)刪除數(shù)據(jù),你不想執(zhí)行了,此時(shí)你們怎么辦?直接停掉整個(gè)服務(wù)嗎?能不能只停掉當(dāng)前線程,而不停掉整個(gè)服務(wù)?答案是可以的,我們給他一個(gè)返回值。這里使用Force Return強(qiáng)制返回即可。

比如這里,twoSum方法要返回
int[],我們直接new int[]{2, 7}返回即可,當(dāng)然我們也可以返回點(diǎn)讓前端開心點(diǎn)的數(shù)據(jù),哄哄前端。
或者我們直接拋出異常也是可以的


多線程調(diào)試
Debug默認(rèn)使用時(shí),會(huì)阻塞所有線程,只留當(dāng)前線程。平時(shí)如果只是學(xué)習(xí)可能不會(huì)覺得麻煩,但是開發(fā)中,某個(gè)接口,其他人在用,難道要?jiǎng)e人等一下,等你Debug完再用嗎,其實(shí)不用的。只需要選中如下所示,即可多線程調(diào)試,沒有打斷點(diǎn)的線程會(huì)直接執(zhí)行,打了斷點(diǎn)的線程,才會(huì)被阻塞,當(dāng)然,同一方法入口,也可以進(jìn)入多個(gè)線程,在Frames中進(jìn)行線程的切換即可。

也可選擇 View Breakpoints,然后再選擇Thread開啟多線程調(diào)度模式。

Stream 調(diào)試
有沒有發(fā)現(xiàn),用Debug調(diào)試Lambda表達(dá)式時(shí)很難受,尤其是哪種一行十幾個(gè)方法哪種,一寫就是一串,寫時(shí)爽的一P,Debug時(shí)反向爽的一P,如果你也有這些問題,那么接下來這個(gè)神器要好好學(xué)習(xí)一下了。
不知你有沒有發(fā)現(xiàn),調(diào)試按鈕里Trace Current Stream Chain 按鈕常年都是黑色的,為什么呢,因?yàn)樗亲鯯tream調(diào)試的,只有斷點(diǎn)在Stream表達(dá)式時(shí)才能使用,使用方法即斷點(diǎn)在Stream時(shí),點(diǎn)擊該按鈕即可。


看出該按鈕的妙處了吧,能直接把每一步的轉(zhuǎn)換方式或者結(jié)果展示在我們面前,而且還有Split Mode展示方式,能一步一步展示轉(zhuǎn)換結(jié)果。

遠(yuǎn)程Debug
當(dāng)我們遇到某些問題,只能在生產(chǎn)環(huán)境下復(fù)現(xiàn),開發(fā)環(huán)境不能復(fù)現(xiàn)的bug時(shí),我們就需要使用遠(yuǎn)程Debug功能了。只需要我們本地代碼和遠(yuǎn)程部署的代碼一致即可。當(dāng)然還要在遠(yuǎn)程啟動(dòng)jar包時(shí),加入-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=xxx參數(shù),其中xxx為監(jiān)聽端口,詳細(xì)步驟如下:
點(diǎn)擊 Edit Configuration…

依次選擇“+”,然后選擇
Remote JVM Debug
然后填寫一下配置,Name是之后啟動(dòng)的名稱,Host 是遠(yuǎn)程服務(wù)器的 ip,port: 用于遠(yuǎn)程socket 連接的端口,注意不能和項(xiàng)目端口一致,否則會(huì)啟動(dòng)失敗,然后,idea 會(huì)為我們自動(dòng)生成一條命令行參數(shù):
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
接著在遠(yuǎn)程啟動(dòng)jar時(shí),加上我們的參數(shù)
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar xxx.jar
在遠(yuǎn)程項(xiàng)目啟動(dòng)成功后, 在本地以Debug方式運(yùn)行第一步配置的服務(wù)

然后再請求遠(yuǎn)程原服務(wù),我們在本地的斷點(diǎn)就起作用了,是不是很秀

