Java Exception性能问题

Java Exception性能问题

背景:
大学里学java,老师口口声声,言之凿凿,告诫我们,Java千万别用异常控制业务流程,只有系统级别的问题,才能使用异常;
(当时,我们都不懂为什么不能用异常,只知道老师这么说,我们就这么做,考试才不会错 :) )
公司里,有两派.异常拥护者说,使用业务异常,代码逻辑更清晰,更OOP;反之者说,使用异常,性能非常糟糕;
(当然,我是拥护者)
论坛上,争论得更多,仁者见仁智者见智,口水很多;
(我不发表意见,其实怎么用,真的都可以)

那么,为什么反对异常呢?貌似大多数人的原因都只有一个:性能差!
使用异常性能真的差吗? 是的!
是什么原因,导致性能问题呢? 那么请看下文...

根本原因在于:
异常基类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.
性能开销在于:
1. 是一个synchronized方法(主因)
2. 需要填充线程运行堆栈信息

但是对于业务异常来说,它只代表业务分支;压根儿不需要stack信息的.如果去掉这个过程,是不是有性能提升呢?

于是做了一个简单的测试对比,对比主体:
1。 创建普通Java对象              (CustomObject extends HashMap)
2。 创建普通Java异常对象          (CustomException extends Exception)
3。 创建改进的Java业务异常对象    (CustomException extends Exception,覆写fillInStackTrace方法,并且去掉同步)

测试结果:
(运行环境:xen虚拟机,5.5G内存,8核;jdk1.6.0_18)
(10个线程,创建10000000个对象所需时间)
普通Java对象         45284 MS
普通java异常        205482 MS
改进的Java业务异常   16731 MS

测试代码如下:
  1  /**
  2   * <pre>
  3   * xen虚拟机,5.5G内存;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 ;                         //  单次循环数量
 18       private   static   final   int              THREADS               =   10 ;                               //  并发线程数量
 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对象.
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对象
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       * 自定义改进的Exception对象 覆写了 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  }



所以,如果我们业务异常的基类,一旦覆写fillInStackTrace,并且去掉同步,那么异常性能有大幅度提升(因为业务异常本身也不需要堆栈信息)


如果说,创建异常的性能开销大家已经有些感觉了,那么TryCatch是否也存在性能开销呢?
接下来,做了一次try...catch 和 if...esle的性能比较

测试结果(运行环境和上面一样):
20个线程,100000000,所消耗的时间:
try...catch:  101412MS
if...else:    100749MS

备注:
在我自己的开发机器上(xp和ubuntu下,单核),try...catch耗时是if...else的2倍(在同一数量级)
具体原因还未知,之后会使用专业的性能测试工具进行分析

测试代码如下:
  1  /**
  2   * <pre>
  3   * xen虚拟机,5.5G内存;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  }


/**
 * <pre>
 * xen虚拟机,5.5G内存;8核CPU
 * LOOP = 10000000
 * THREADS = 10
 * o:       45284 
 * e:       205482 
 * exte:    16731
 * </pre>
 * 
 * k
 * 
 * 
@author li.jinl 2010-7-9 上午09:16:14
 
*/
public class NewExceptionTester {

    
private static final int             LOOP                 = 10000000;                        // 单次循环数量
    private static final int             THREADS              = 10;                              // 并发线程数量

    
private static final List<Long>      newObjectTimes       = new ArrayList<Long>(THREADS);
    
private static final List<Long>      newExceptionTimes    = new ArrayList<Long>(THREADS);
    
private static final List<Long>      newExtExceptionTimes = new ArrayList<Long>(THREADS);

    
private static final ExecutorService POOL                 = Executors.newFixedThreadPool(30);

    
public static void main(String[] args) throws Exception {
        List
<Callable<Boolean>> all = new ArrayList<Callable<Boolean>>();
        all.addAll(tasks(
new NewObject()));
        all.addAll(tasks(
new NewException()));
        all.addAll(tasks(
new NewExtException()));

        POOL.invokeAll(all);

        System.out.println(
"o:\t\t" + total(newObjectTimes));
        System.out.println(
"e:\t\t" + total(newExceptionTimes));
        System.out.println(
"exte:\t\t" + total(newExtExceptionTimes));

        POOL.shutdown();
    }

    
private static List<Callable<Boolean>> tasks(Callable<Boolean> c) {
        List
<Callable<Boolean>> list = new ArrayList<Callable<Boolean>>(THREADS);
        
for (int i = 0; i < THREADS; i++) {
            list.add(c);
        }
        
return list;
    }

    
private static long total(List<Long> list) {
        
long sum = 0;
        
for (Long v : list) {
            sum 
+= v;
        }
        
return sum;
    }

    
public static class NewObject implements Callable<Boolean> {

        @Override
        
public Boolean call() throws Exception {
            
long start = System.currentTimeMillis();
            
for (int i = 0; i < LOOP; i++) {
                
new CustomObject("");
            }
            newObjectTimes.add(System.currentTimeMillis() 
- start);
            
return true;
        }

    }

    
public static class NewException implements Callable<Boolean> {

        @Override
        
public Boolean call() throws Exception {
            
long start = System.currentTimeMillis();
            
for (int i = 0; i < LOOP; i++) {
                
new CustomException("");
            }
            newExceptionTimes.add(System.currentTimeMillis() 
- start);
            
return true;
        }

    }

    
public static class NewExtException implements Callable<Boolean> {

        @Override
        
public Boolean call() throws Exception {
            
long start = System.currentTimeMillis();
            
for (int i = 0; i < LOOP; i++) {
                
new ExtCustomException("");
            }
            newExtExceptionTimes.add(System.currentTimeMillis() 
- start);
            
return true;
        }

    }

    
/**
     * 自定义java对象.
     * 
     * 
@author li.jinl 2010-7-9 上午11:28:27
     
*/
    
public static class CustomObject extends HashMap {

        
private static final long serialVersionUID = 5176739397156548105L;

        
private String            message;

        
public CustomObject(String message){
            
this.message = message;
        }

        
public String getMessage() {
            
return message;
        }

        
public void setMessage(String message) {
            
this.message = message;
        }

    }

    
/**
     * 自定义普通的Exception对象
     * 
     * 
@author li.jinl 2010-7-9 上午11:28:58
     
*/
    
public static class CustomException extends Exception {

        
private static final long serialVersionUID = -6879298763723247455L;

        
private String            message;

        
public CustomException(String message){
            
this.message = message;
        }

        
public String getMessage() {
            
return message;
        }

        
public void setMessage(String message) {
            
this.message = message;
        }

    }

    
/**
     * <pre>
     * 自定义改进的Exception对象 覆写了 fillInStackTrace方法
     * 1. 不填充stack
     * 2. 取消同步
     * </pre>
     * 
     * 
@author li.jinl 2010-7-9 上午11:29:12
     
*/
    
public static class ExtCustomException extends Exception {

        
private static final long serialVersionUID = -6879298763723247455L;

        
private String            message;

        
public ExtCustomException(String message){
            
this.message = message;
        }

        
public String getMessage() {
            
return message;
        }

        
public void setMessage(String message) {
            
this.message = message;
        }

        @Override
        
public Throwable fillInStackTrace() {
            
return this;
        }
    }

 

结论:
1。Exception的性能是差,原因在于ThrowablefillInStackTrace()方法
2. 可以通过改写业务异常基类的方法,提升性能
3。try...catch和if...else的性能开销在同一数量级

4。至于是否使用异常进行业务逻辑的控制,主要看代码风格.(我个人挺喜欢业务异常的)


备注:
以上测试比较简单,写得也比较急.此文也写得比较急(工作时间偷偷写).如果分析不到位的地方,请指出.

你可能感兴趣的:(Java Exception性能问题)