stone2083

          Java Exception性能問(wèn)題

          背景:
          大學(xué)里學(xué)java,老師口口聲聲,言之鑿鑿,告誡我們,Java千萬(wàn)別用異常控制業(yè)務(wù)流程,只有系統(tǒng)級(jí)別的問(wèn)題,才能使用異常;
          (當(dāng)時(shí),我們都不懂為什么不能用異常,只知道老師這么說(shuō),我們就這么做,考試才不會(huì)錯(cuò) :) )
          公司里,有兩派.異常擁護(hù)者說(shuō),使用業(yè)務(wù)異常,代碼邏輯更清晰,更OOP;反之者說(shuō),使用異常,性能非常糟糕;
          (當(dāng)然,我是擁護(hù)者)
          論壇上,爭(zhēng)論得更多,仁者見(jiàn)仁智者見(jiàn)智,口水很多;
          (我不發(fā)表意見(jiàn),其實(shí)怎么用,真的都可以)

          那么,為什么反對(duì)異常呢?貌似大多數(shù)人的原因都只有一個(gè):性能差!
          使用異常性能真的差嗎? 是的!
          是什么原因,導(dǎo)致性能問(wèn)題呢? 那么請(qǐng)看下文...

          根本原因在于:
          異常基類Throwable.java的public synchronized native Throwable fillInStackTrace()方法
          方法介紹:
          Fills in the execution stack trace. This method records within this Throwable object information about the current state of the stack frames for the current thread.
          性能開(kāi)銷在于:
          1. 是一個(gè)synchronized方法(主因)
          2. 需要填充線程運(yùn)行堆棧信息

          但是對(duì)于業(yè)務(wù)異常來(lái)說(shuō),它只代表業(yè)務(wù)分支;壓根兒不需要stack信息的.如果去掉這個(gè)過(guò)程,是不是有性能提升呢?

          于是做了一個(gè)簡(jiǎn)單的測(cè)試對(duì)比,對(duì)比主體:
          1。 創(chuàng)建普通Java對(duì)象              (CustomObject extends HashMap)
          2。 創(chuàng)建普通Java異常對(duì)象          (CustomException extends Exception)
          3。 創(chuàng)建改進(jìn)的Java業(yè)務(wù)異常對(duì)象    (CustomException extends Exception,覆寫(xiě)fillInStackTrace方法,并且去掉同步)

          測(cè)試結(jié)果:
          (運(yùn)行環(huán)境:xen虛擬機(jī),5.5G內(nèi)存,8核;jdk1.6.0_18)
          (10個(gè)線程,創(chuàng)建10000000個(gè)對(duì)象所需時(shí)間)
          普通Java對(duì)象         45284 MS
          普通java異常        205482 MS
          改進(jìn)的Java業(yè)務(wù)異常   16731 MS

          測(cè)試代碼如下:
            1 /**
            2  * <pre>
            3  * xen虛擬機(jī),5.5G內(nèi)存;8核CPU
            4  * LOOP = 10000000
            5  * THREADS = 10
            6  * o:       45284 
            7  * e:       205482 
            8  * exte:    16731
            9  * </pre>
           10  * 
           11  * k
           12  * 
           13  * @author li.jinl 2010-7-9 上午09:16:14
           14  */
           15 public class NewExceptionTester {
           16 
           17     private static final int             LOOP                 = 10000000;                        // 單次循環(huán)數(shù)量
           18     private static final int             THREADS              = 10;                              // 并發(fā)線程數(shù)量
           19 
           20     private static final List<Long>      newObjectTimes       = new ArrayList<Long>(THREADS);
           21     private static final List<Long>      newExceptionTimes    = new ArrayList<Long>(THREADS);
           22     private static final List<Long>      newExtExceptionTimes = new ArrayList<Long>(THREADS);
           23 
           24     private static final ExecutorService POOL                 = Executors.newFixedThreadPool(30);
           25 
           26     public static void main(String[] args) throws Exception {
           27         List<Callable<Boolean>> all = new ArrayList<Callable<Boolean>>();
           28         all.addAll(tasks(new NewObject()));
           29         all.addAll(tasks(new NewException()));
           30         all.addAll(tasks(new NewExtException()));
           31 
           32         POOL.invokeAll(all);
           33 
           34         System.out.println("o:\t\t" + total(newObjectTimes));
           35         System.out.println("e:\t\t" + total(newExceptionTimes));
           36         System.out.println("exte:\t\t" + total(newExtExceptionTimes));
           37 
           38         POOL.shutdown();
           39     }
           40 
           41     private static List<Callable<Boolean>> tasks(Callable<Boolean> c) {
           42         List<Callable<Boolean>> list = new ArrayList<Callable<Boolean>>(THREADS);
           43         for (int i = 0; i < THREADS; i++) {
           44             list.add(c);
           45         }
           46         return list;
           47     }
           48 
           49     private static long total(List<Long> list) {
           50         long sum = 0;
           51         for (Long v : list) {
           52             sum += v;
           53         }
           54         return sum;
           55     }
           56 
           57     public static class NewObject implements Callable<Boolean> {
           58 
           59         @Override
           60         public Boolean call() throws Exception {
           61             long start = System.currentTimeMillis();
           62             for (int i = 0; i < LOOP; i++) {
           63                 new CustomObject("");
           64             }
           65             newObjectTimes.add(System.currentTimeMillis() - start);
           66             return true;
           67         }
           68 
           69     }
           70 
           71     public static class NewException implements Callable<Boolean> {
           72 
           73         @Override
           74         public Boolean call() throws Exception {
           75             long start = System.currentTimeMillis();
           76             for (int i = 0; i < LOOP; i++) {
           77                 new CustomException("");
           78             }
           79             newExceptionTimes.add(System.currentTimeMillis() - start);
           80             return true;
           81         }
           82 
           83     }
           84 
           85     public static class NewExtException implements Callable<Boolean> {
           86 
           87         @Override
           88         public Boolean call() throws Exception {
           89             long start = System.currentTimeMillis();
           90             for (int i = 0; i < LOOP; i++) {
           91                 new ExtCustomException("");
           92             }
           93             newExtExceptionTimes.add(System.currentTimeMillis() - start);
           94             return true;
           95         }
           96 
           97     }
           98 
           99     /**
          100      * 自定義java對(duì)象.
          101      * 
          102      * @author li.jinl 2010-7-9 上午11:28:27
          103      */
          104     public static class CustomObject extends HashMap {
          105 
          106         private static final long serialVersionUID = 5176739397156548105L;
          107 
          108         private String            message;
          109 
          110         public CustomObject(String message){
          111             this.message = message;
          112         }
          113 
          114         public String getMessage() {
          115             return message;
          116         }
          117 
          118         public void setMessage(String message) {
          119             this.message = message;
          120         }
          121 
          122     }
          123 
          124     /**
          125      * 自定義普通的Exception對(duì)象
          126      * 
          127      * @author li.jinl 2010-7-9 上午11:28:58
          128      */
          129     public static class CustomException extends Exception {
          130 
          131         private static final long serialVersionUID = -6879298763723247455L;
          132 
          133         private String            message;
          134 
          135         public CustomException(String message){
          136             this.message = message;
          137         }
          138 
          139         public String getMessage() {
          140             return message;
          141         }
          142 
          143         public void setMessage(String message) {
          144             this.message = message;
          145         }
          146 
          147     }
          148 
          149     /**
          150      * <pre>
          151      * 自定義改進(jìn)的Exception對(duì)象 覆寫(xiě)了 fillInStackTrace方法
          152      * 1. 不填充stack
          153      * 2. 取消同步
          154      * </pre>
          155      * 
          156      * @author li.jinl 2010-7-9 上午11:29:12
          157      */
          158     public static class ExtCustomException extends Exception {
          159 
          160         private static final long serialVersionUID = -6879298763723247455L;
          161 
          162         private String            message;
          163 
          164         public ExtCustomException(String message){
          165             this.message = message;
          166         }
          167 
          168         public String getMessage() {
          169             return message;
          170         }
          171 
          172         public void setMessage(String message) {
          173             this.message = message;
          174         }
          175 
          176         @Override
          177         public Throwable fillInStackTrace() {
          178             return this;
          179         }
          180     }
          181 }



          所以,如果我們業(yè)務(wù)異常的基類,一旦覆寫(xiě)fillInStackTrace,并且去掉同步,那么異常性能有大幅度提升(因?yàn)闃I(yè)務(wù)異常本身也不需要堆棧信息)


          如果說(shuō),創(chuàng)建異常的性能開(kāi)銷大家已經(jīng)有些感覺(jué)了,那么TryCatch是否也存在性能開(kāi)銷呢?
          接下來(lái),做了一次try...catch 和 if...esle的性能比較

          測(cè)試結(jié)果(運(yùn)行環(huán)境和上面一樣):
          20個(gè)線程,100000000,所消耗的時(shí)間:
          try...catch:  101412MS
          if...else:    100749MS

          備注:
          在我自己的開(kāi)發(fā)機(jī)器上(xp和ubuntu下,單核),try...catch耗時(shí)是if...else的2倍(在同一數(shù)量級(jí))
          具體原因還未知,之后會(huì)使用專業(yè)的性能測(cè)試工具進(jìn)行分析

          測(cè)試代碼如下:
            1 /**
            2  * <pre>
            3  * xen虛擬機(jī),5.5G內(nèi)存;8核CPU
            4  * LOOP = 100000000
            5  * THREADS = 20
            6  * 
            7  * tc:  101412
            8  * ie:  100749
            9  * </pre>
           10  * 
           11  * @author li.jinl 2010-7-9 上午10:47:56
           12  */
           13 public class ProcessTester {
           14 
           15     private static final int             LOOP          = 100000000;
           16     private static final int             THREADS       = 20;
           17 
           18     private static final List<Long>      tryCatchTimes = new ArrayList<Long>(THREADS);
           19     private static final List<Long>      ifElseTimes   = new ArrayList<Long>(THREADS);
           20 
           21     private static final ExecutorService POOL          = Executors.newFixedThreadPool(40);
           22 
           23     public static void main(String[] args) throws Exception {
           24         List<Callable<Boolean>> all = new ArrayList<Callable<Boolean>>();
           25         all.addAll(tasks(new TryCatch()));
           26         all.addAll(tasks(new IfElse()));
           27 
           28         POOL.invokeAll(all);
           29 
           30         System.out.println("tc:\t\t" + total(tryCatchTimes));
           31         System.out.println("ie:\t\t" + total(ifElseTimes));
           32 
           33         POOL.shutdown();
           34     }
           35 
           36     private static List<Callable<Boolean>> tasks(Callable<Boolean> c) {
           37         List<Callable<Boolean>> list = new ArrayList<Callable<Boolean>>(THREADS);
           38         for (int i = 0; i < THREADS; i++) {
           39             list.add(c);
           40         }
           41         return list;
           42     }
           43 
           44     private static long total(List<Long> list) {
           45         long sum = 0;
           46         for (Long v : list) {
           47             sum += v;
           48         }
           49         return sum;
           50     }
           51 
           52     public static class TryCatch implements Callable<Boolean> {
           53 
           54         @Override
           55         public Boolean call() throws Exception {
           56             long start = System.currentTimeMillis();
           57             for (int i = 0; i < LOOP; i++) {
           58                 try {
           59                     exception();
           60                     // 
           61                 } catch (ExtCustomException e) {
           62                     // 
           63                 }
           64             }
           65             tryCatchTimes.add(System.currentTimeMillis() - start);
           66             return true;
           67         }
           68 
           69         private void exception() throws ExtCustomException {
           70             throw new ExtCustomException("");
           71         }
           72 
           73     }
           74 
           75     public static class IfElse implements Callable<Boolean> {
           76 
           77         @Override
           78         public Boolean call() throws Exception {
           79             long start = System.currentTimeMillis();
           80             for (int i = 0; i < LOOP; i++) {
           81                 Exception e = exception();
           82                 if (e instanceof ExtCustomException) {
           83                     // 
           84                 }
           85             }
           86             ifElseTimes.add(System.currentTimeMillis() - start);
           87             return true;
           88         }
           89 
           90         private Exception exception() {
           91             return new ExtCustomException("");
           92         }
           93 
           94     }
           95 
           96     public static class ExtCustomException extends Exception {
           97 
           98         private static final long serialVersionUID = -6879298763723247455L;
           99 
          100         private String            message;
          101 
          102         public ExtCustomException(String message){
          103             this.message = message;
          104         }
          105 
          106         public String getMessage() {
          107             return message;
          108         }
          109 
          110         public void setMessage(String message) {
          111             this.message = message;
          112         }
          113 
          114         @Override
          115         public Throwable fillInStackTrace() {
          116             return this;
          117         }
          118 
          119     }
          120 
          121 }


          結(jié)論:
          1。Exception的性能是差,原因在于ThrowablefillInStackTrace()方法
          2. 可以通過(guò)改寫(xiě)業(yè)務(wù)異常基類的方法,提升性能
          3。try...catch和if...else的性能開(kāi)銷在同一數(shù)量級(jí)

          4。至于是否使用異常進(jìn)行業(yè)務(wù)邏輯的控制,主要看代碼風(fēng)格.(我個(gè)人挺喜歡業(yè)務(wù)異常的)


          備注:
          以上測(cè)試比較簡(jiǎn)單,寫(xiě)得也比較急.此文也寫(xiě)得比較急(工作時(shí)間偷偷寫(xiě)).如果分析不到位的地方,請(qǐng)指出.

          posted on 2010-07-09 14:30 stone2083 閱讀(13756) 評(píng)論(16)  編輯  收藏 所屬分類: java

          Feedback

          # re: Java Exception性能問(wèn)題[未登錄](méi) 2010-07-09 16:50 Jacky

          值得借鑒 3Q  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2010-07-09 16:54 去改變

          學(xué)習(xí)了  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2010-07-09 18:45 rox

          一直知道異常過(guò)多,會(huì)有問(wèn)題,但不知道為什么?
          謝謝了!  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2010-08-12 17:58 啊寶

          我們的系統(tǒng)也是用異常做的控制業(yè)務(wù)流程的,提到異常就煩  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2010-08-13 09:08 stone2083

          @啊寶
          控制流程用Exception(try..catch)也好,還是ResultModel(if..else)也罷.只是不同的設(shè)計(jì)理念而已.
          都可以,都對(duì)

          至于提到異常煩,如果是為性能問(wèn)題.我以為大可不必.
          1.異常的性能沒(méi)那么差.在上面的測(cè)試中,一個(gè)異常的產(chǎn)生,0.02ms而已
          2.大多數(shù)的應(yīng)用,對(duì)性能要求并非很高
          3.引起性能瓶頸的,往往是不合理的設(shè)計(jì),錯(cuò)誤的使用同步等業(yè)務(wù)代碼產(chǎn)生的.
          4.實(shí)在不行,就是用改進(jìn)后的異常

          如果再不行,那么只好拋棄業(yè)務(wù)異常吧
          如果再不行,那么只好拋棄java改用c等語(yǔ)言吧.

          選擇一種語(yǔ)言也好,選擇一種設(shè)計(jì)也罷,只是為了更好的處理需求而已.

          至于上文,只是為了描述異常的本質(zhì).在了解原理的基礎(chǔ)上,讓業(yè)務(wù)異常的使用不出現(xiàn)性能的浪費(fèi)而已.
          絕不是表明,異常性能真得對(duì)系統(tǒng)產(chǎn)生了影響 :)
            回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2012-09-20 12:18 alswl

          感謝分析,知道為什么異常引起瓶頸了  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2012-11-27 17:07 fillInStackTrace的 synchronized 無(wú)影響

          測(cè)試 new Exception() 時(shí),我多測(cè)試一個(gè)自定義異常,在其中覆寫(xiě),fillInStackTrace,但是,帶著 synchronized 修飾符。運(yùn)行結(jié)果顯示:是否帶有 synchronized 修飾符 對(duì)耗時(shí)沒(méi)有任何影響

          win7
          java version "1.6.0_37"
          Java(TM) SE Runtime Environment (build 1.6.0_37-b06)
          Java HotSpot(TM) 64-Bit Server VM (build 20.12-b01, mixed mode)  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2012-11-27 17:10 fillInStackTrace的 synchronized 無(wú)影響

          https://blogs.oracle.com/jrose/entry/longjumps_considered_inexpensive
          這里面說(shuō):主要是 creation 浪費(fèi)時(shí)間,throw 不浪費(fèi)時(shí)間(我覺(jué)得,這和你的 try-catch, if-else 測(cè)試是吻合的)  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2012-11-28 11:27 stone2083

          @fillInStackTrace的 synchronized 無(wú)影響
          throw對(duì)于jvm來(lái)說(shuō),只是一條athrow指令,將異常壓棧出棧而已。所以開(kāi)銷非常小。

          至于synchronized測(cè)試,能否將你的測(cè)試代碼貼一下(是在多線程條件下測(cè)試的嗎?)
          我之前的數(shù)據(jù)沒(méi)有了,這次新做了測(cè)試,差異還是很大的。
          20個(gè)線程下:
          Opt Take Time: 1372
          Gen Take Time: 36510

          50個(gè)線程下:
          Opt Take Time: 3906
          Gen Take Time: 88240

          mac jdk 1.6 64bit  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2013-01-20 01:17 weipeng

          @stone2083
          我這邊單線程測(cè)試,不使用同步的已經(jīng)比使用同步的快了約3倍。所以同步還是有影響的,畢竟有monitor的進(jìn)入和退出。

          ps:
          業(yè)務(wù)異常我也比較喜歡,但是就是有篡改失敗的結(jié)果的可能,比如偷懶catch了Exception。
          try {
          // member 不存在
          throw new BusinessException(Xxx);

          // offer不存在
          throw new BusinessException(Yyy);

          // 調(diào)用一些業(yè)務(wù)接口,如 xxx.create(param); 但是它throws 自定義異常
          } catch (Exception) { // 對(duì)捕獲到業(yè)務(wù)接口的異常,想返回系統(tǒng)錯(cuò)誤
          throw new BusinessException("SystemError...");
          }

          這樣就造成了,如果member不存在,那么就返回系統(tǒng)錯(cuò)誤了,而不是原來(lái)想的member不存在的業(yè)務(wù)編碼了。

          所以,我覺(jué)得如果使用業(yè)務(wù)異常來(lái)做控制,那么調(diào)用端,在調(diào)用時(shí)可以避免對(duì)錯(cuò)誤的判空,而且層次感通過(guò)catch來(lái)的比較養(yǎng)眼。但是有點(diǎn)劣勢(shì)的就是要求在實(shí)現(xiàn)內(nèi)部對(duì)于異常的處理要求非常統(tǒng)一。  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2013-01-21 11:03 stone2083

          @weipeng
          確實(shí),存在如你所說(shuō)的問(wèn)題。
          但是問(wèn)題的本質(zhì)還在于使用者,忽視業(yè)務(wù)分支邏輯導(dǎo)致的。
          即便使用If Else,也存在類似的問(wèn)題(只是發(fā)生普遍性相對(duì)會(huì)小),如:
          if(result.isMemberNotFound || result.isOfferNotFound) {
          System.out.println("Sys Error.");
          }

          我現(xiàn)在到不糾結(jié)使用哪種形式,唯一要求是:同個(gè)項(xiàng)目?jī)?nèi)部要保持風(fēng)格統(tǒng)一,并且規(guī)范使用。  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題[未登錄](méi) 2013-12-17 16:07 呵呵

          異常不應(yīng)該被用來(lái)做業(yè)務(wù),即使通過(guò)改寫(xiě)也一樣,同樣需要?jiǎng)?chuàng)建一個(gè)實(shí)例,new一個(gè)實(shí)例同樣是不小的開(kāi)銷,虛擬機(jī)還要對(duì)他進(jìn)行回收。如果異常情況反復(fù)出現(xiàn),同樣會(huì)大量消耗系統(tǒng)資源。異常當(dāng)然是用來(lái)做異常的,異常字面意思就是系統(tǒng)運(yùn)行正常的時(shí)候不應(yīng)該出現(xiàn)的,如果出現(xiàn)了才被認(rèn)為是異常。所以異常應(yīng)該是很少出現(xiàn)的,由于使用try-catch減少了if判斷,提升了性能。所以異常是為了提升性能的,不是用來(lái)消耗性能的。  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2014-08-15 16:08 wyc

          classloader不是就是用異常來(lái)處理業(yè)務(wù)的么,自己定義的類肯定要通過(guò)異常截獲來(lái)加載  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2014-11-24 17:50 zuidaima

          java 異常相關(guān)demo源代碼下載:http://zuidaima.com/share/k%E5%BC%82%E5%B8%B8-p1-s1.htm  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題[未登錄](méi) 2015-12-16 14:48 小明

          沒(méi)有并發(fā),測(cè)試準(zhǔn)么?  回復(fù)  更多評(píng)論   

          # re: Java Exception性能問(wèn)題 2015-12-16 19:18 stone2083

          @小明
          private static final ExecutorService POOL = Executors.newFixedThreadPool(30);  回復(fù)  更多評(píng)論   

          主站蜘蛛池模板: 罗定市| 乌拉特前旗| 宣恩县| 新泰市| 介休市| 曲沃县| 英德市| 稷山县| 色达县| 济阳县| 招远市| 威远县| 灌云县| 兴山县| 武功县| 正蓝旗| 宁德市| 莆田市| 商水县| 襄城县| 汕尾市| 湘阴县| 任丘市| 呼和浩特市| 双鸭山市| 黄浦区| 闵行区| 六安市| 雷州市| 江源县| 喀喇沁旗| 台山市| 朝阳市| 金华市| 依安县| 德钦县| 民权县| 渭源县| 林周县| 自贡市| 鄂托克前旗|