博客 / 詳情

返回

✨Try-Catch✨竟然會影響性能

大家好,我是半夏之沫 😁😁 一名金融科技領域的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異常表)我們可知,只有指令05在執行過程中拋出了Exception,才會跳轉到指令6開始執行,換言之只要不拋出異常,那麼在執行完指令5後方法就結束了,此時和沒添加Try-Catch時的代碼執行鏈路是一樣的,也就是不拋出異常時,Try-Catch不會影響程序性能。

我們添加Try-Catch,其實就是為了做異常處理,也就是我們天然的認為被Try-Catch的代碼就是會拋出異常的,而異常一旦發生,此時程序性能就會受到一定程度的影響,表現在如下兩個方面。

  1. 異常對象創建有性能開銷。具體表現在異常對象創建時會去爬棧得到方法調用鏈路信息
  2. 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其實不會影響程序性能,因為在沒有異常發生時,代碼執行鏈路不會加深。

但是如果出現異常,那麼程序性能就會受到影響,表現在如下兩個方面。

  1. 異常對象創建有性能開銷。具體表現在異常對象創建時會去爬棧得到方法調用鏈路信息
  2. Try-Catch捕獲到異常後會讓代碼執行鏈路變深。

因此在日常開發中,可以適當增加防禦性編程來防止JVM拋出異常,也建議儘量將主動的異常拋出替換為提前返回響應,總之就是儘量減少非必要的異常出現。


大家好,我是半夏之沫 😁😁 一名金融科技領域的JAVA系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈

user avatar edagarli 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.