大家好,我是半夏之沫 😁😁 一名金融科技領域的JAVA系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實,最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈
前言
一朋友問我Try-Catch寫多了會不會讓程序變慢,我不加思索的回答肯定不會,畢竟曾經研究過Java異常相關的字節碼指令,只要被Try-Catch的代碼不拋出異常,那麼代碼執行鏈路是不會加深的。
可事後我反覆思考這個看似簡單實則也不復雜的問題,我覺得順着這個問題往下,還有一些東西可以思考,如果你感興趣,那就跟隨本文的視角一起來看下吧。
正文
首先鄭重聲明,單純的針對一段代碼添加Try-Catch,是 不會 影響性能的,我們可以通過下面的示例代碼並結合字節碼指令來看下。
示例代碼如下所示。
public class TryCatchPerformance {
public Response execute(String state) {
return innerHandle(state);
}
public Response innerHandle(String state) {
// todo 暫無邏輯
return null;
}
public static class Response {
private int state;
public Response(int state) {
this.state = state;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
}
我們依次執行如下語句為上述代碼生成字節碼指令。
# 編譯Java文件
javac .\TryCatchPerformance.java
# 反彙編字節碼文件
javap -c .\TryCatchPerformance.class
可以得到execute() 方法的字節碼指令如下。
public com.lee.learn.exception.TryCatchPerformance$Response execute(java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokevirtual #2 // Method innerHandle:(Ljava/lang/String;)Lcom/lee/learn/exception/TryCatchPerformance$Response;
5: areturn
現在對execute() 方法添加Try-Catch,如下所示。
public class TryCatchPerformance {
public Response execute(String state) {
try {
return innerHandle(state);
} catch (Exception e) {
return new Response(500);
}
}
public Response innerHandle(String state) {
// todo 暫無邏輯
return null;
}
public static class Response {
private int state;
public Response(int state) {
this.state = state;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
}
查看execute() 方法的字節碼指令如下所示。
public com.lee.learn.exception.TryCatchPerformance$Response execute(java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokevirtual #2 // Method innerHandle:(Ljava/lang/String;)Lcom/lee/learn/exception/TryCatchPerformance$Response;
5: areturn
6: astore_2
7: new #4 // class com/lee/learn/exception/TryCatchPerformance$Response
10: dup
11: sipush 500
14: invokespecial #5 // Method com/lee/learn/exception/TryCatchPerformance$Response."<init>":(I)V
17: areturn
Exception table:
from to target type
0 5 6 Class java/lang/Exception
雖然添加Try-Catch後,字節碼指令增加了很多條,但是通過Exception table(異常表)我們可知,只有指令0到5在執行過程中拋出了Exception,才會跳轉到指令6開始執行,換言之只要不拋出異常,那麼在執行完指令5後方法就結束了,此時和沒添加Try-Catch時的代碼執行鏈路是一樣的,也就是不拋出異常時,Try-Catch不會影響程序性能。
我們添加Try-Catch,其實就是為了做異常處理,也就是我們天然的認為被Try-Catch的代碼就是會拋出異常的,而異常一旦發生,此時程序性能就會受到一定程度的影響,表現在如下兩個方面。
- 異常對象創建有性能開銷。具體表現在異常對象創建時會去爬棧得到方法調用鏈路信息;
- Try-Catch捕獲到異常後會讓代碼執行鏈路變深。
由此可見Try-Catch其實不會影響程序性能,但是異常的出現的的確確會影響,無論是JVM創建的異常,還是我們在代碼中new出來的異常,都是會影響性能的。
所以現在我們來看看如下代碼有什麼可以優化的地方。
public class TryCatchPerformance {
public Response execute(String state) {
try {
return innerHandle(state);
} catch (Exception e) {
return new Response(500);
}
}
public Response innerHandle(String state) {
if (state == null || state.isEmpty()) {
// 通過異常中斷執行
throw new IllegalStateException();
} else if ("success".equals(state)) {
return new Response(200);
} else {
return new Response(400);
}
}
public static class Response {
private int state;
public Response(int state) {
this.state = state;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
}
上述代碼的問題出現在innerHandle() ,仗着調用方有Try-Catch做異常處理,就在入參非法時通過創建異常來中斷執行,我相信在實際的工程開發中,很多時候大家都是這麼幹的,因為有統一異常處理,那麼通過拋出異常來中斷執行並在統一異常處理的地方返回響應,是一件再平常不過的事情了,但是通過前面的分析我們知道,創建異常有性能開銷,捕獲異常並處理也有性能開銷,這些性能開銷其實是可以避免的,例如下面這樣。
public class TryCatchPerformance {
public Response execute(String state) {
try {
return innerHandle(state);
} catch (Exception e) {
return new Response(500);
}
}
public Response innerHandle(String state) {
if (state == null || state.isEmpty()) {
// 通過提前返回響應的方式中斷執行
return new Response(500);
} else if ("success".equals(state)) {
return new Response(200);
} else {
return new Response(400);
}
}
public static class Response {
private int state;
public Response(int state) {
this.state = state;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
}
如果當某個分支執行到了,我們也確切的知道該分支下的響應是什麼,此時直接返回響應,相較於拋出異常後在統一異常處理那裏返回響應,性能會更好。
總結
Try-Catch其實不會影響程序性能,因為在沒有異常發生時,代碼執行鏈路不會加深。
但是如果出現異常,那麼程序性能就會受到影響,表現在如下兩個方面。
- 異常對象創建有性能開銷。具體表現在異常對象創建時會去爬棧得到方法調用鏈路信息;
- Try-Catch捕獲到異常後會讓代碼執行鏈路變深。
因此在日常開發中,可以適當增加防禦性編程來防止JVM拋出異常,也建議儘量將主動的異常拋出替換為提前返回響應,總之就是儘量減少非必要的異常出現。
大家好,我是半夏之沫 😁😁 一名金融科技領域的JAVA系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實,最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈