原创

通过字节码分析java的try catch放在循环内外的问题

关于java的try catch放在循环内外的问题,看过挺多文章的;记得15年看过一篇文章说try catch语句千万不要写在循环语句内,不然会降低循环语句的性能,在很长的一段时间里也是这样做的,其实呢,当时已经用java7了,编译的字节码文件已经对try catch使用异常表优化了,性能上其实两者已经没有多大区别了。

下面我们通过字节码来分析try catch在循环体内外的异同。

先贴示例代码

public class TestTryCatch {
    
    public void tryCatchOutsideWhile(int n) {
        try {
            while (n > 0) {
                System.out.println(n);
                n--;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void tryCatchWithinWhile(int n) {
        while (n > 0) {
            try {
                System.out.println(n);
                n--;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

字节码: 

图片的说明

    红框表示while语句块内语句         蓝框 1 表示while的条件语句            黄框表示异常语句块内语句

try catch在循环体内外的异同

相同点

    字节码上的第1 - 7 行字节码本质上一样的;蓝框 1 表示的while条件语句的意思是条件不成立则跳出while循环体。

不同点

    try catch在循环体外时(看tryCatchOutsideWhile字节码):

          1.  蓝框 1 的while条件语句条件不成立时,跳出循环体多了一步,先跳转到蓝框 2 再跳出循环体。

          2.  黄框的异常语句块在循环体外,如果while语句块内发生异常,则直接退出循环,跳转到对应的异常语句块入口执行异常语句块。

    try catch在循环体内时(看tryCatchWithinWhile字节码):

          1.  蓝框 1 的while条件语句条件不成立时,直接跳出循环体。

          2.  黄框的异常语句块在循环体内,如果while语句块内发生异常,直接跳转到对应的异常语句块入口执行异常语句块;最后执行蓝框 2 的语句goto 0继续执行while剩下的循环。

总结:

    根据上面的分析:在跳出循环体时,try catch在循环体内比在体外少跳转一步。从两者性能上说,没什么差别,多跳转一步的性能损耗可以忽略不计。

    在要求循环体出异常后继续执行剩下的循环时,使用try catch在循环体内;

    在要求循环体出异常后立即退出循环体时,使用try catch在循环体外。

 

题外话:性能与测试

    看了一篇关于try catch放在循环体内外的性能测试文章,因为忽略了对JVM的预热,所以测试结果是不正确的。在对自己代码性能测试时千万记住不能忽略这个前提条件,下面是他的测试代码与测试结果,看上去前提条件是一样的,相同的机器与环境,但忽略了一点,在我们自己的机器上测试时,程序启动时JVM没有预热,程序所需要使用的类与字符串常量的环境没有完全加载与初始化,对于第一个执行的方法method1方法运行时间比较长,而后面的方法由于相应的类与环境都以预热,运行的时间都比较短。

/**
 * 示例取自下面的文章:
 * https://blog.csdn.net/xinhui88/article/details/8281611
 * 用这示例想说明测试时不要忽略对JVM的预热,不然不能正确反映测试结果。
 * 对于这一点向原作者说声抱歉!
 *
 * 测试结果为: 
 * JDK7: 
 * method1 total: 9846  【放在循环外】 
 * method2 total: 1266  【放在循环内】 
 * method3 total: 1523  【不使用try catch】 
 *
 * JDK6: 
 * method1 total: 3457  【放在循环外】 
 * method2 total: 3280  【放在循环内】 
 * method3 total: 1323  【不使用try catch】
 */
public class Main{

    public static void main(String[] args)
    {
        Main ins = new Main();
        int size = 10000000;

        ins.method1(size);
        ins.method2(size);
        ins.method3(size);
    }
    public void method1(int size)
    {
        long start = System.currentTimeMillis();
        ArrayList<String> al = new ArrayList<String>();
        String str = null;
        try
        {
            for (int i = 0; i < size; i++)
            {
                str = "str" + i;
                al.add(str);
            }
        }
        catch (Exception e)
        {
        }
        System.out.println("method1 total: " + (System.currentTimeMillis() - start));
    }
    public void method2(int size)
    {
        long start = System.currentTimeMillis();
        ArrayList<String> al = new ArrayList<String>();
        String str = null;
        for (int i = 0; i < size; i++)
        {
            try
            {
                str = "str" + i;
                al.add(str);
            }
            catch (Exception e)
            {
            }
        }
        System.out.println("method2 total: " + (System.currentTimeMillis() - start));
    }
    public void method3(int size)
    {
        long start = System.currentTimeMillis();
        ArrayList<String> al = new ArrayList<String>();
        String str = null;
        for (int i = 0; i < size; i++)
        {
            str = "str" + i;
            al.add(str);
        }
        System.out.println("method3 total: " + (System.currentTimeMillis() - start));
    }
}

下面在我的机器上通过调换方法的执行顺序来说明JVM预热对测试的影响:

method1 total: 6392
method2 total: 1258
method3 total: 1468

---------------------------

method2 total: 6399
method1 total: 1164
method3 total: 1545

---------------------------

method2 total: 6385
method3 total: 1134
method1 total: 1458

---------------------------

method1 total: 6331
method1 total: 1173
method2 total: 1448
method3 total: 1288

从最后一组数据来看,通过增加method1方法进行预热(这里主要是方法使用到的字符串常量的加载)后,后面1、2、3数据比较正常,当然从1、2、3方法上看程序还是有抖动,因为我的win10测试环境开了太多其他程序,对测试结果有影响,这也是测试时要考虑的因素,所以要对测试的不同方法位置也要一样(比如要测试的方法都调到第二个位置),还要进行多次掐头去尾再求平均值。

 

 

 

正文到此结束
本文目录