Java中各類IDE的Debug功能,都是經過Java提供的Java Platform Debugger Architecture (JPDA)來實現的。html
藉助Debug功能,能夠很方便的調試程序,快速的模擬/找到程序中的錯誤。java
Interllij Idea的Debug功能上說雖然看起來和Eclipse差很少,可是在使用體驗上,仍是要比Eclipse好了很多。spring
Debug中,最經常使用的莫過於下一步,下一個斷點(Breakpoint),查看運行中的值幾個操做;可是除了這些IDE還提供了一些「高級」的功能,能夠幫助咱們更方便的進行調試編程
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過程的功能安全
在Debug的過程當中,通常狀況下,讓程序正常執行便可。可是某些狀況下,須要動態的修改執行流程,此時若是經過修改代碼的方式仍是太不方便了,好在Idea提供了一些動態修改程序執行流程的功能,可讓咱們很靈活的進行調試springboot
當咱們在Debug時出現手抖等狀況,提早或按錯了下一步,致使錯過了斷點。此時能夠經過Idea提供的Drop Frame功能,來返回到上一個棧幀服務器
虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀(Stack Frame)[插圖]用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
其實不光是Java,其餘編程語言的方法執行模型,也是一個棧結構,方法的執行對應着一次push/pop的操做網絡
好比下面這段代碼,當執行過一次方法後,棧幀上有兩個方法多線程
此時,點擊Drop Frame按鈕後,會刪除棧頂上的數據,回到調用log方法前的位置
注意:Drop Frame雖然好用,可是可能在Drop Frame以後發生一些不可逆的問題,好比IO類的操做,或已修改的共享變量是沒法回滾的,由於這個操做只是刪除棧頂的棧幀,並非真正的「逆向運行」
當一個方法比較長,或者Step Info到一個不過重要的方法想跳過該方法時,能夠經過Force Return功能來強制結束該方法
注意:Force Return和Step Out不同,Step Out是跳出當前步驟,仍是會執行方法中的代碼;而Force Return是直接強制結束方法, 跳過該方法後的全部代碼直接返回。好比下面這段代碼,當使用Force Return後,evaluate方法中的println並不會執行
當要強制返回的方法有返回值時(非void),force return還須要指定一個返回值
當調用的方法可能拋出異常,調用者須要處理異常時,能夠直接讓方法拋出異常而不用修改代碼
下面是一段僞代碼,模擬發送請求,超時自動重試
當方法執行至sendPacket時,能夠執行Throw Exception操做,提早結束方法並拋出指定的異常
調用者收到異常後,就能夠執行catch中的重試邏輯了,這樣以來就不用經過修改程序等操做來模擬異常,很是的方便
當應用程序沒法在Idea中運行,又想Debug這個運行中的程序時,能夠經過Attach to Process功能,該功能能夠Debug作到調試運行中的程序,固然前提是,保證這個正在運行的JVM進程代碼和Idea中的代碼一致
這種場景其實挺常見的,好比你要調試springboot executable jar時,或者調試tomcat源碼等獨立部署運行的進程,經過Attach to Process就很是方便了,能夠作到用Idea以外的環境+Idea中的代碼進行Debug
這種功能其實在C/C++ GDB下也有,Debug正在運行的程序而已,Intellij Clion也是支持的
遠程調試是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:
而後配置好Host/Port,點擊Apply保存便可
最後,先啓動遠程的JVM進程,而後在Idea中已Debug來運行剛纔配置的Configuration便可
小提示:遠程調試下,因爲有網絡的開銷,反應會比較慢,並且會致使遠程程序的暫停,使用時請找一個沒有人使用的環境
多線程程序是比較難寫的,確切的說是很難調試,一個不當心就會由於線程安全的問題引發各類Bug,而且這些Bug還可能很難復現。因爲操做系統的線程調度是咱們沒法控制的,因此多線程程序的錯誤有很大的隨機性,一旦出現問題很難找到;咱們的程序可能在99.99%的狀況下都是正常的,可是最後的0.01%也極可能形成嚴重的錯誤
線程安全的最多見問題就是競爭條件,當某些數據被多個線程同時修改時,就可能會發生線程安全問題
好比下面這個流程,正常狀況下程序沒問題
當出現了競爭問題,單個線程的read和write操做之間,調度了其餘線程,此時數據就會出錯
下面是一段示例代碼,雖然共享數據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能夠將掛起粒度設置爲線程,而不是整個引用
Suspend設置爲Thread後,以下圖所示,將斷點打在a.add這一行,而後以Debug模式運行程序後,主線程和新建的線程都會掛在addIfAbsent方法中,咱們能夠在Idea中的Debug面板中切換線程
此時,Main線程和子線程都已經調用了contains方法,並都返回false,掛起在a.add這一行,都準備將17添加到a中
執行下一步後,Main線程成功的將17添加到集合中
此時切換到Thread-0線程,仍是掛在a.add(x)這一行,可是集合a中已經有元素17了,但時Thread-0線程仍是會繼續add,add以後集合a就出現了重複元素17,致使程序出現了bug
從上面的例子能夠看出,在調試多線程程序的過程當中,利用Idea Debug的Suspend功能,能夠很方便的模擬多線程競爭的問題,這對於編寫或調試多線程程序實在太方便了