stone2083

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 }


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

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


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

posted on 2010-07-09 14:30 stone2083 阅读(13725) 评论(16)  编辑  收藏 所属分类: java

Feedback

# re: Java Exception性能问题[未登录] 2010-07-09 16:50 Jacky

值得借鉴 3Q  回复  更多评论   

# re: Java Exception性能问题 2010-07-09 16:54 去改变

学习了  回复  更多评论   

# re: Java Exception性能问题 2010-07-09 18:45 rox

一直知道异常过多,会有问题,但不知道为什么?
谢谢了!  回复  更多评论   

# re: Java Exception性能问题 2010-08-12 17:58 啊宝

我们的系统也是用异常做的控制业务流程的,提到异常就烦  回复  更多评论   

# re: Java Exception性能问题 2010-08-13 09:08 stone2083

@啊宝
控制流程用Exception(try..catch)也好,还是ResultModel(if..else)也罢.只是不同的设计理念而已.
都可以,都对

至于提到异常烦,如果是为性能问题.我以为大可不必.
1.异常的性能没那么差.在上面的测试中,一个异常的产生,0.02ms而已
2.大多数的应用,对性能要求并非很高
3.引起性能瓶颈的,往往是不合理的设计,错误的使用同步等业务代码产生的.
4.实在不行,就是用改进后的异常

如果再不行,那么只好抛弃业务异常吧
如果再不行,那么只好抛弃java改用c等语言吧.

选择一种语言也好,选择一种设计也罢,只是为了更好的处理需求而已.

至于上文,只是为了描述异常的本质.在了解原理的基础上,让业务异常的使用不出现性能的浪费而已.
绝不是表明,异常性能真得对系统产生了影响 :)
  回复  更多评论   

# re: Java Exception性能问题 2012-09-20 12:18 alswl

感谢分析,知道为什么异常引起瓶颈了  回复  更多评论   

# re: Java Exception性能问题 2012-11-27 17:07 fillInStackTrace的 synchronized 无影响

测试 new Exception() 时,我多测试一个自定义异常,在其中覆写,fillInStackTrace,但是,带着 synchronized 修饰符。运行结果显示:是否带有 synchronized 修饰符 对耗时没有任何影响

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)  回复  更多评论   

# re: Java Exception性能问题 2012-11-27 17:10 fillInStackTrace的 synchronized 无影响

https://blogs.oracle.com/jrose/entry/longjumps_considered_inexpensive
这里面说:主要是 creation 浪费时间,throw 不浪费时间(我觉得,这和你的 try-catch, if-else 测试是吻合的)  回复  更多评论   

# re: Java Exception性能问题 2012-11-28 11:27 stone2083

@fillInStackTrace的 synchronized 无影响
throw对于jvm来说,只是一条athrow指令,将异常压栈出栈而已。所以开销非常小。

至于synchronized测试,能否将你的测试代码贴一下(是在多线程条件下测试的吗?)
我之前的数据没有了,这次新做了测试,差异还是很大的。
20个线程下:
Opt Take Time: 1372
Gen Take Time: 36510

50个线程下:
Opt Take Time: 3906
Gen Take Time: 88240

mac jdk 1.6 64bit  回复  更多评论   

# re: Java Exception性能问题 2013-01-20 01:17 weipeng

@stone2083
我这边单线程测试,不使用同步的已经比使用同步的快了约3倍。所以同步还是有影响的,毕竟有monitor的进入和退出。

ps:
业务异常我也比较喜欢,但是就是有篡改失败的结果的可能,比如偷懒catch了Exception。
try {
// member 不存在
throw new BusinessException(Xxx);

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

// 调用一些业务接口,如 xxx.create(param); 但是它throws 自定义异常
} catch (Exception) { // 对捕获到业务接口的异常,想返回系统错误
throw new BusinessException("SystemError...");
}

这样就造成了,如果member不存在,那么就返回系统错误了,而不是原来想的member不存在的业务编码了。

所以,我觉得如果使用业务异常来做控制,那么调用端,在调用时可以避免对错误的判空,而且层次感通过catch来的比较养眼。但是有点劣势的就是要求在实现内部对于异常的处理要求非常统一。  回复  更多评论   

# re: Java Exception性能问题 2013-01-21 11:03 stone2083

@weipeng
确实,存在如你所说的问题。
但是问题的本质还在于使用者,忽视业务分支逻辑导致的。
即便使用If Else,也存在类似的问题(只是发生普遍性相对会小),如:
if(result.isMemberNotFound || result.isOfferNotFound) {
System.out.println("Sys Error.");
}

我现在到不纠结使用哪种形式,唯一要求是:同个项目内部要保持风格统一,并且规范使用。  回复  更多评论   

# re: Java Exception性能问题[未登录] 2013-12-17 16:07 呵呵

异常不应该被用来做业务,即使通过改写也一样,同样需要创建一个实例,new一个实例同样是不小的开销,虚拟机还要对他进行回收。如果异常情况反复出现,同样会大量消耗系统资源。异常当然是用来做异常的,异常字面意思就是系统运行正常的时候不应该出现的,如果出现了才被认为是异常。所以异常应该是很少出现的,由于使用try-catch减少了if判断,提升了性能。所以异常是为了提升性能的,不是用来消耗性能的。  回复  更多评论   

# re: Java Exception性能问题 2014-08-15 16:08 wyc

classloader不是就是用异常来处理业务的么,自己定义的类肯定要通过异常截获来加载  回复  更多评论   

# re: Java Exception性能问题 2014-11-24 17:50 zuidaima

java 异常相关demo源代码下载:http://zuidaima.com/share/k%E5%BC%82%E5%B8%B8-p1-s1.htm  回复  更多评论   

# re: Java Exception性能问题[未登录] 2015-12-16 14:48 小明

没有并发,测试准么?  回复  更多评论   

# re: Java Exception性能问题 2015-12-16 19:18 stone2083

@小明
private static final ExecutorService POOL = Executors.newFixedThreadPool(30);  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航: