多種高級debug方法,幫你更快定位問題

Java中各類IDE的Debug功能,都是經過Java提供的Java Platform Debugger Architecture (JPDA)來實現的。html

藉助Debug功能,能夠很方便的調試程序,快速的模擬/找到程序中的錯誤。java

Interllij Idea的Debug功能上說雖然看起來和Eclipse差很少,可是在使用體驗上,仍是要比Eclipse好了很多。spring

Debug中,最經常使用的莫過於下一步,下一個斷點(Breakpoint),查看運行中的值幾個操做;可是除了這些IDE還提供了一些「高級」的功能,能夠幫助咱們更方便的進行調試編程

Java8 Streams Debug

Stream 做爲 Java 8 的一大亮點,它和 java.io 包裏的 InputStream 和 OutputStream 是徹底不一樣的概念。Java 8 中的 Stream 是對集合(Collection)對象功能的加強,它專一於對集合對象進行各類很是便利、高效的聚合操做(aggregate operation),或者大批量數據操做 (bulk data operation)。tomcat

IntStream.iterate(1, n -> n + 1)
                .skip(100)
                .limit(100)
                .filter(PrimeFinder::isPrime)//檢查是不是素數
                .forEach(System.out::println);

上面這段代碼,就是一個streams的常見用法,對集合排序並轉換取值。Idea也提供了分析streams過程的功能安全

image.png
image.png
image.png

修改程序執行流程

在Debug的過程當中,通常狀況下,讓程序正常執行便可。可是某些狀況下,須要動態的修改執行流程,此時若是經過修改代碼的方式仍是太不方便了,好在Idea提供了一些動態修改程序執行流程的功能,可讓咱們很靈活的進行調試springboot

返回上一個棧幀/刪除當前棧幀/「逆向運行」(Drop frame)

當咱們在Debug時出現手抖等狀況,提早或按錯了下一步,致使錯過了斷點。此時能夠經過Idea提供的Drop Frame功能,來返回到上一個棧幀服務器

虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀(Stack Frame)[插圖]用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。

其實不光是Java,其餘編程語言的方法執行模型,也是一個棧結構,方法的執行對應着一次push/pop的操做網絡

好比下面這段代碼,當執行過一次方法後,棧幀上有兩個方法多線程

image.png

image.png

此時,點擊Drop Frame按鈕後,會刪除棧頂上的數據,回到調用log方法前的位置

image.png

注意:Drop Frame雖然好用,可是可能在Drop Frame以後發生一些不可逆的問題,好比IO類的操做,或已修改的共享變量是沒法回滾的,由於這個操做只是刪除棧頂的棧幀,並非真正的「逆向運行」

強制方法返回(Force Return)

當一個方法比較長,或者Step Info到一個不過重要的方法想跳過該方法時,能夠經過Force Return功能來強制結束該方法

WeChat Image_20200625170601.png

注意:Force Return和Step Out不同,Step Out是跳出當前步驟,仍是會執行方法中的代碼;而Force Return是直接強制結束方法, 跳過該方法後的全部代碼直接返回。好比下面這段代碼,當使用Force Return後,evaluate方法中的println並不會執行

當要強制返回的方法有返回值時(非void),force return還須要指定一個返回值

image.png

觸發異常

當調用的方法可能拋出異常,調用者須要處理異常時,能夠直接讓方法拋出異常而不用修改代碼

下面是一段僞代碼,模擬發送請求,超時自動重試

image.png

當方法執行至sendPacket時,能夠執行Throw Exception操做,提早結束方法並拋出指定的異常

WeChat Image_20200625170601.png

image.png

調用者收到異常後,就能夠執行catch中的重試邏輯了,這樣以來就不用經過修改程序等操做來模擬異常,很是的方便

Debug運行中的JVM進程(Attach to Process)

當應用程序沒法在Idea中運行,又想Debug這個運行中的程序時,能夠經過Attach to Process功能,該功能能夠Debug作到調試運行中的程序,固然前提是,保證這個正在運行的JVM進程代碼和Idea中的代碼一致

image.png

這種場景其實挺常見的,好比你要調試springboot executable jar時,或者調試tomcat源碼等獨立部署運行的進程,經過Attach to Process就很是方便了,能夠作到用Idea以外的環境+Idea中的代碼進行Debug

這種功能其實在C/C++ GDB下也有,Debug正在運行的程序而已,Intellij Clion也是支持的

遠程調試(Remote Debug)

遠程調試是JVM提供的功能,和上面的Attach to Process相似,只是這個進程從本地變成遠程了

好比咱們的程序在本地沒有問題,在服務器上卻有問題;好比本地是MacOs,服務器是Centos,環境的不一樣致使出現某些Bug,此時就能夠經過遠程調試功能來調試

若是要啓用遠程調試,須要在遠程JVM進程的啓動腳本中添加如下參數:

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005

suspend參數表示,JVM進程是否已「掛起」模式啓動,若是以「掛起」模式啓動,JVM進程會一直阻塞不繼續執行,直到遠程調試器鏈接到該進程爲止

這個參數很是有用,好比咱們的問題是在JVM啓動期間發生的(好比Spring的加載/初始化流程),就須要將suspend設置爲y,這樣JVM進程就會等待Ide中的遠程調試鏈接完成纔會繼續運行。不然遠程的JVM已經運行了一段時間了,Ide的Debugger才鏈接,早已經錯過了斷點的時機

在遠程JVM進程配置完成Debug模式並啓動完成後,就能夠在Idea中鏈接了,在Idea的Run/Debug Configurations面板中新建一個Remote的Configuration:

image.png

而後配置好Host/Port,點擊Apply保存便可

image.png

最後,先啓動遠程的JVM進程,而後在Idea中已Debug來運行剛纔配置的Configuration便可

image.png

小提示:遠程調試下,因爲有網絡的開銷,反應會比較慢,並且會致使遠程程序的暫停,使用時請找一個沒有人使用的環境

多線程下的調試

多線程程序是比較難寫的,確切的說是很難調試,一個不當心就會由於線程安全的問題引發各類Bug,而且這些Bug還可能很難復現。因爲操做系統的線程調度是咱們沒法控制的,因此多線程程序的錯誤有很大的隨機性,一旦出現問題很難找到;咱們的程序可能在99.99%的狀況下都是正常的,可是最後的0.01%也極可能形成嚴重的錯誤

線程安全的最多見問題就是競爭條件,當某些數據被多個線程同時修改時,就可能會發生線程安全問題

好比下面這個流程,正常狀況下程序沒問題

image.png

當出現了競爭問題,單個線程的read和write操做之間,調度了其餘線程,此時數據就會出錯

image.png

下面是一段示例代碼,雖然共享數據a是一個synchronizedList,可是它並不能保證addIfAbsent是個原子操做,由於contains和add是兩個synchronized方法,兩個方法的執行間隙間仍是有可能被其餘線程修改

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ConcurrencyTest {
    static final List a = Collections.synchronizedList(new ArrayList());

    public static void main(String[] args) {
        Thread t = new Thread(() -> addIfAbsent(17));
        t.start();
        addIfAbsent(17);
        t.join();
        System.out.println(a);
    }

    private static void addIfAbsent(int x) {
        if (!a.contains(x)) {
            a.add(x);
        }
    }
}

若是對這段代碼進行Debug時,一個Step Over( 下一步)以後,這個下一步操做的做用域是整個進程,而不是當前進程。也就是說,Debug下一步以後,極可能被其餘線程插入並執行了修改,這個共享數據a同樣不安全,極可能出現重複添加元素17的問題

可是上述問題只是可能出現,實際調試時很難復現。Idea的Debug能夠將掛起粒度設置爲線程,而不是整個引用

image.png

Suspend設置爲Thread後,以下圖所示,將斷點打在a.add這一行,而後以Debug模式運行程序後,主線程和新建的線程都會掛在addIfAbsent方法中,咱們能夠在Idea中的Debug面板中切換線程

image.png

此時,Main線程和子線程都已經調用了contains方法,並都返回false,掛起在a.add這一行,都準備將17添加到a中

image.png

執行下一步後,Main線程成功的將17添加到集合中

image.png

此時切換到Thread-0線程,仍是掛在a.add(x)這一行,可是集合a中已經有元素17了,但時Thread-0線程仍是會繼續add,add以後集合a就出現了重複元素17,致使程序出現了bug

image.png

WeChat Image_20200625170601.png

從上面的例子能夠看出,在調試多線程程序的過程當中,利用Idea Debug的Suspend功能,能夠很方便的模擬多線程競爭的問題,這對於編寫或調試多線程程序實在太方便了

參考

相關文章
相關標籤/搜索