前言

在此之前,常常听说到在开发中,尽量不要使用try-catch来捕获异常,这样会对性能的消耗非常大,并对其称之为“企业级项目开发规范”。那么,我们本着学习的态度,来看看在Java项目中,使用try-catch对性能的影响。

try-catch对性能的影响

0x01通过测试来检测try-catch对性能的影响

我们新建了一个SpringBoot项目,并在其中定义了三个控制器,分别是没有使用try做异常捕获的、使用try但没有捕获到异常的、使用try且捕获到异常的三个方法。

    @GetMapping("/notry")
    public String noTry(){
        String str = "{\"name\":\"liuxincode\"}";
        long startTime=System.currentTimeMillis();
        JSONObject jsonObject = JSONObject.parseObject(str);
        long endTime=System.currentTimeMillis();
        return "未使用try捕获异常的运行时间: "+(endTime - startTime)+"ms";

    }

    @GetMapping("/noexception")
    public String noException(){
        String str = "{\"name\":\"liuxincode\"}";
        long startTime=System.currentTimeMillis();
        try{
            JSONObject jsonObject = JSONObject.parseObject(str);
        }catch (Exception e){
            e.printStackTrace();
        }
        long endTime=System.currentTimeMillis();
        return "使用try但未捕获异常的运行时间: "+(endTime - startTime)+"ms";
    }

    @GetMapping("error")
    public String error(){
        String str = "{\"name:\"liuxincode\"}";
        long startTime=System.currentTimeMillis();
        try{
            JSONObject jsonObject = JSONObject.parseObject(str);
        }catch (Exception e){
            e.printStackTrace();
        }
        long endTime=System.currentTimeMillis();
        return "使用try且捕获到异常的运行时间: "+(endTime - startTime)+"ms";
    }

为了避免误差,我们在访问完每个控制器后都去重启SpringBoot项目,下面是三个控制器的返回结果:
image.png
image.png
image.png
通过上面的返回结果,我们看到好像对性能的差别不大,甚至存在可控误差范围内,因此,我们将try捕获异常次数调大,看看捕获10W次异常的差别:
为了防止缓存,我们在没每次进行格式化时都生成一个新的字符串:

    @GetMapping("/notry")
    public String noTry(){
        long startTime=System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            String str = String.format("{\"name\":\"liuxincode%s\"}",i);
            JSONObject jsonObject = JSONObject.parseObject(str);
        }
        long endTime=System.currentTimeMillis();
        return "未使用try捕获异常的运行时间: "+(endTime - startTime)+"ms";

    }

    @GetMapping("/noexception")
    public String noException(){
        long startTime=System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            try{
                String str = String.format("{\"name\":\"liuxincode%s\"}",i);
                JSONObject jsonObject = JSONObject.parseObject(str);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        long endTime=System.currentTimeMillis();
        return "使用try但未捕获异常的运行时间: "+(endTime - startTime)+"ms";
    }

    @GetMapping("error")
    public String error(){
        long startTime=System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            try{
                String str = String.format("{\"name\":\"liuxincode%s\"}",i);
                JSONObject jsonObject = JSONObject.parseObject(str);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        long endTime=System.currentTimeMillis();
        return "使用try且捕获到异常的运行时间: "+(endTime - startTime)+"ms";
    }

image.png
image.png
image.png
可能因为缓存等因素的影响,我们的程序运行时间远小于预期时间,不过我们也可以看到,尽管使用了try去捕获异常,无论有没有捕获到异常,程序的运行时间并没没有太大的影响,也就是说对于程序的性能影响并不大。

0x02 查找原因

为什么在我们的程序中,不管是否使用了try-catch,对程序的运行时间没什么影响呢?首先,我们查阅了业内比较认可的《Java开发手册(泰山版)》,发现在整本书中,使用try来捕获异常的示例代码还是比较多的,在书中,有关于try和性能的规范,我们只查阅到了这样一句话:
image.png
这段话并没有强调说不要使用try来进行异常捕获,仅仅是表达了不要滥用try-catch而已。

接着,我们又在网上查阅了相关资料,发现了下面的一段话:

  1. 类会跟随一张 异常表(exception table),每一个try-catch都会在这个表里添加行记录,每一个记录都有4个信息(try-catch的开始地址,结束地址,异常的处理起始位,异常类名称)。
  2. 当代码在运行时抛出了异常时,首先拿着抛出位置到异常表中查找是否可以被catch(例如看位置是不是处于任何一栏中的开始和结束位置之间),如果可以则跑到异常处理的起始位置开始处理,如果没有找到则原地return,并且copy异常的引用给父调用方,接着看父调用的异常表。。。以此类推。

通过这段话我们知道,在程序运行过程中,使用了try去捕获异常的话,如果没有捕获异常,那么程序会继续运行,对程序的性能并没有影响;如果运行过程中捕获到了异常,那么会拿抛出异常的地址去比对异常表,这个比对只是比对两个地址间的差异而已,因此对性能的影响也不大。

0x03 总结

总的来说,在开发过程中,我们可以放心的使用try-catch来捕获异常,这对性能的影响并不大(小声逼逼:总好过代码报错),但这也并不意味着我们可以滥用try,在不必要的地方,我们尽量少使用try,这样不经有利于代码的美观,也符合Java开发规范。