【Java】Debug斷點除錯常用技巧

Nemo&發表於2021-06-20

Debug操作技巧

Show Execution Pointimage

將游標回到當前斷點停頓的地方
image

Step Overimage

執行當前行程式碼,並將執行進度跳轉到下一行。

Step Intoimage

進入到當前程式碼行的方法內部。
image

image

Step Outimage

從方法內部出去
image

image

Force Step Intoimage

強制進入Java自帶方法的內部
image

image

Run to Cursorimage

image

將游標定位到想到達的程式碼行
image

點選Run to Cursor
image

Drop Frameimage

丟棄當前虛擬機器棧幀

初始:
image

進入方法:
image

丟棄當前幀:
image

也就是說,我們退回了上一步進入方法之前。

Evaluate Expressionimage

可以用它來評估表示式
image
如 p.getName()等。
image

Force Return | 避免操作資源

我們在除錯程式碼的時候中間出現了異常,但是我們又沒有做異常捕獲,稀裡糊塗地把錯誤資料存到了資料庫中,我們又需要將這些資料給刪除,將資料庫復原,才能達到之前我們需要的效果。

所以,接下來我們講一講如何避免操作資源,強制返回。

public static void saveResource() {
    System.out.println("shit happens");
    
    System.out.println("save to db");
    System.out.println("save to redis");
    System.out.println("send message to mq for money payout");
}

debug:
image

我們發現程式出現了異常
image

Force Return
image

它會只列印shit happens,不會繼續向下執行了。
image

Trace Current Stream Chain | Stream Debugimage

public static void streamDebug() {
    // stream chain
    Arrays.asList(1, 2, 3, 45).stream()
            .filter(i -> i % 2 == 0 || i % 3 == 0)
            .map(i -> i * i)
            .forEach(System.out::print);
}

image

image

image

image

左下角平鋪模式Flat Mode:
image

斷點常用技巧

斷點(Breakpoint)

斷點:如果把程式想象成一條平滑的線,那麼斷點就是一個結,可以讓程式中斷在需要的地方,從而方便其分析。

設定斷點:在程式碼裡需要除錯的地方,滑鼠雙擊程式碼行號的左邊,再次雙擊即可取消斷點。

在除錯中可以設定的斷點型別有五種:

  • 行斷點:
    spring在註冊Bean定義(registerBeanDefinition)時,如果是org.springframework.demo.MyBean,就掛起執行緒,可以開始單步除錯了。
    對於命中次數(hit count)的使用,一般是在迴圈中,第N個物件的處理有問題,設定hit count = N, 重除錯時,可以方便到達需要除錯的迴圈次數時,停下來除錯。

  • 方法斷點:
    方法斷點的好處是可以從方法方法進入或者退出時停下來除錯,類似行斷點,而且只有行斷點和方法斷點有條件和訪問次數的設定功能。
    但是方法斷點還有另外一個好處,如果程式碼編譯時,指定不攜帶除錯資訊,行斷點是不起作用的,只能打方法斷點。
    有興趣的可以將Add line number…前的勾去掉,除錯下看看。

  • 觀察斷點:
    在成員變數上打的斷點。只有物件成員變數有效果,靜態成員變數不起作用。
    可以設定變數被訪問或者設定的時候掛起執行緒/VM。

  • 異常斷點:
    系統發生異常時,在被捕獲異常的丟擲位置處或者程式未捕獲的異常丟擲處掛起執行緒/VM, 也可以指定是否包括異常的子類也被檢測。

  • 類載入斷點:
    在類名上打的斷點。介面上是打不了類載入斷點的,但是抽象類是可以的,只是在除錯的時候,斷點不會明顯進入classloader中,單步進入知會進入到子類的構造方法中,非抽象類在掛起執行緒後單步進入就會到classloader中(如果沒有filter過濾掉的話)。類載入斷點不管是打在抽象或者非抽象類上,都會在類第一次載入或者第一個子類第一次被載入時,掛起執行緒/VM。

注意:每種斷點的設定有些許不一樣,可以在斷點上右鍵->Breakpoint properties進行設定,但一般在斷點視窗有快速設定的介面,Breakpoint properties中多了filter, 其實比較雞肋,用處不大。

除錯狀態

啟動服務開始除錯:

  • 方法一:例如上圖的程式碼中,滑鼠點選main方法-->右鍵Debug As-->Java Application開始java程式碼除錯;
  • 方法二:直接點選“除錯”按鈕,即點選小瓢蟲邊上的倒三角,選擇Debug As-->Java Application;
  • 方法三:快捷鍵F11;方法四,選單欄選擇Run-->Debug,還有其他方法此處不再贅述了。

    開發工具首次除錯會彈出提示,需要切換到Debug工作區,勾選“Remember my decision”,下次便不再提示。

除錯執行:
【Java】Debug斷點除錯常用技巧

功能 快捷鍵 描述 備註
Step Info F5 單步進入(如果有方法呼叫,將進入呼叫方法中進行除錯) 逐語句
Step Over F6 單步跳過(不進入行的任何方法呼叫中,直接執行完當前程式碼行,並跳到下一行) 逐過程
Step Return F7 單步返回(執行完當前方法,並從呼叫棧中彈出當前方法,返回當前方法被呼叫處) 跳出
Resume F8 恢復正常執行(直到遇到下一個斷點) 繼續執行
Run to Line Ctrl+R 執行到當前行(將忽略中間所有斷點,執行到當前游標所在行)
Drop To Frame 回退到指定方法開始處執行,這個功能相當贊。
在方法呼叫棧上的某個方法右鍵,選擇Drop To Frame就可以從該方法的開始處執行,比如 重新執行本方法,可以在本方法上用Drop To Frame,將從本方法的第一行重新執行。
當然對於有副作用的方法,比如 資料庫操作,更改傳入引數的物件內容等操作可能重新執行就不再是你想要的內容了。
Copy Stack 拷貝當前執行緒棧資訊

斷點

public class BreakPointDemo {
    // 行斷點
    public static void line() {
        System.out.println("this is the line break point");
    }
    
    // 詳細斷點(源斷點)
    public static void detailLine() {
        System.out.println("this is the detail line break point");
    }
    
    // 方法斷點 | 介面跳轉實現類
    public static void method() {
        System.out.println("this is from method");
        IService iservice = new IServiceImpl();
        iservice.execute();
    }
    
    // 異常斷點 | 全域性捕獲
    public static void exception() {
        Object o = null;
        o.toString();
        System.out.println("this line will never be print!");
    }
    
    // 欄位斷點 | 讀寫監控
    public static void field() {
        Person p = new Person("field", 10);
        p.setAge(12);
        System.out.println(p);
    }
    
    public static void main(String[] args) {
        line();
        detailLine();
        method();
        exception();
        field();
    }
}

行斷點

    // 行斷點
    public static void line() {
        System.out.println("this is the line break point");
    }

使用滑鼠左鍵點選程式碼左側:
image

右鍵點選行斷點,我們也可以進行一些斷點停頓的條件設定:
image

如 i == 20等條件。

Suspend也可以選擇執行緒模式,我們可以切換不同的執行緒,來觀察不同執行緒的該語句的執行效果。(如果是All的話,那就是哪一個執行緒先過來,那就是哪個執行緒)
image

詳細斷點

    // 詳細斷點(源斷點)
    public static void detailLine() {
        System.out.println("this is the detail line break point");
    }

SHIFT+滑鼠左鍵:
image
image

debug:
image

方法斷點 | 介面跳轉實現類

方法斷點 = 方法起始行斷點 + 方法結尾行斷點

    // 方法斷點 | 介面跳轉實現類
    public static void method() {
        System.out.println("this is from method");
        IService iservice = new IServiceImpl();
        iservice.execute();
    }

在方法上打斷點:
image

debug:
第一個斷點停留在方法體內第一行程式碼:
image

第二個斷點停留在方法體內返回的最後一行程式碼:
image


在介面方法上打斷點:
image

真正執行的是介面方法的實現類:
image

如果我們有很多的實現類,我們具體不知道是哪一個,我們只需要在介面方法上打一個斷點,它就會自動地跳到介面具體的實現類方法上。

異常斷點 | 全域性捕獲

    // 異常斷點 | 全域性捕獲
    public static void exception() {
        Object o = null;
        o.toString();
        System.out.println("this line will never be print!");
    }

異常斷點會停頓在報出異常的具體程式碼行。

  1. 點選View Breakpoints
    image

  2. 在異常斷點處新增新的異常斷點
    image
    image
    image
    image

  3. 接下來,只要你的程式遇到空指標異常,它就會停頓到發出空指標異常的那一行程式碼那裡。

沒有顯式打斷點:
image

debug:
image

這個異常斷點對於我們異常除錯很方便。

欄位斷點 | 讀寫監控

    // 欄位斷點 | 讀寫監控
    public static void field() {
        Person p = new Person("field", 10);
        p.setAge(12);
        System.out.println(p);
    }

在類的欄位屬性上打斷點:
image

我們在欄位左邊打了一個欄位斷點(小眼睛),它就會去監控該欄位屬性的整個生命週期的值的變化。

dubug:
第一個:構造方法修改了屬性值
image

第二個:setter方法修改了屬性值
image

相關文章