问题:Java中的异常处理真的很慢吗?

传统观点,以及许多谷歌搜索结果都认为,Java中的正常程序流不应该使用异常逻辑.通常会给出两个原因,

  1. 它真的很慢——甚至比常规代码慢一个数量级(给出的原因各不相同),

  1. it is messy because people expect only errors to be h和led in exceptional code.

这个问题是关于#1的.

As an example, this page describes Java exception h和ling as "very slow" 和 relates the slowness to the creation of the exception message string - "this string is then used in creating the exception object that is thrown. This is not fast." The article Effective Exception H和ling in Java says that "the reason for this is due to the object creation aspect of exception h和ling, which thereby makes throwing exceptions inherently slow". Another reason out there is that the stack trace generation is what slows it down.

My testing (using Java 1.6.0_07, Java HotSpot 10.0, on 32 bit Linux), indicates that exception h和ling is no slower than regular code. I tried running a method in a loop that executes some code. At the end of the method, I use a boolean to indicate whether to return or throw. This way the actual processing is the same. I tried running the methods in different orders 和 averaging my test times, thinking it may have been the JVM warming up. In all my tests, the throw was at least as fast as the return, if not faster (up to 3.1% faster). I am completely open to the possibility that my tests were wrong, but I haven't seen anything out there in the way of the code sample, test comparisons, or results in the last year or two that show exception h和ling in Java to actually be slow.

bootstrap 我走上这条道路的是我需要使用的API,它将异常作为正常控制逻辑的一部分抛出.我想纠正它们的用法,但现在我可能无法纠正.我会不得不称赞他们的前瞻性思维吗?

In the paper Efficient Java exception h和ling in just-in-time compilation, the authors suggest that the presence of exception h和lers alone, even if no exceptions are thrown, is enough to prevent the JIT compiler from optimizing the code properly, thus slowing it down. I haven't tested this theory yet.

推荐答案

这取决于如何实现异常.最简单的方法是使用setjmp和long jmp.这意味着CPU的所有寄存器都被写入堆栈(这已经需要一些时间),并且可能需要创建一些其他数据……所有这些都已经在try语句中发生了.Throw语句需要展开堆栈并恢复所有寄存器的值(以及VM中可能的其他值).因此try 和抛出一样慢,这是相当慢的,但是如果没有抛出异常,退出try 挡路在大多数情况下都不需要任何时间(因为所有东西都放在堆栈上,如果方法存在,堆栈会自动清理).

Sun和其他人认识到,这可能是次优的,当然,随着时间的推移,虚拟机的速度越来越快.还有另一种实现异常的方法,它使try本身闪电般地快(实际上try一般不会发生任何事情——当VM加载类时,所有需要发生的事情都已经完成了),并且它使throw没有那么慢.我不知道哪个JVM使用这种新的、更好的技术...

...但是,您是在用Java编写代码,以便以后的代码只在一个特定系统的一个JVM上运行吗?既然它可能在任何其他平台或任何其他JVM版本(可能是任何其他供应商的版本)上运行,谁说他们也使用fast实现?快的比慢的复杂,而且不容易在所有系统上实现.你想随身携带吗?那就别指望异常会很快出现.

这也会让你在一个try块中做什么有很大的不同.如果您打开一个try块,并且从未从该try块中调用任何方法,那么try块将非常快,因为JIT实际上可以像处理简单的goto一样处理一次抛出.如果抛出异常,它既不需要保存堆栈状态,也不需要释放堆栈(只需要跳转到catch处理程序).然而,这不是你通常做的.通常你打开一个try块,然后调用一个可能引发异常的方法,对吗?即使你只是在你的方法中使用try块,这是什么样的方法,不调用任何其他方法?它会计算一个数字吗?那你为什么需要例外呢?有很多更优雅的方法来调节程序流.对于除简单数学之外的几乎任何东西,都必须调用外部方法,这已经 destruct 了本地try块的优势.

请参阅以下测试代码:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

结果:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

try 挡路的放缓幅度太小,不能排除后台进程等混杂因素.但是抓到的挡路杀死了一切,使它慢了66倍!

正如我所说的,如果您将try/catch和抛出都放在同一个方法(Method 3)中,结果将不会那么糟糕,但这是一个我不会依赖的特殊JIT优化.即使在使用这种优化的情况下,投掷仍然相当缓慢.所以我不知道您在这里想做什么,但是肯定有一种比使用try/catch/jo更好的方法.

Java相关问答推荐

javafx getHostServices(). showDocument()调出Chrome而不是默认浏览器(Linux)

Java 21虚拟线程会解决转向react 式单线程框架的主要原因吗?

ApachePOI:不带换行的新行

Jlink选项&-strie-ative-Commands";的作用是什么?

Kubernetes的Java客户端检索状态.处于终止状态的Pod的阶段';正在运行';

无法在WebSocket onMessage中捕获错误

由于我在Main方法中关闭了 scanner ,但在该方法中创建了一个新的 scanner ,因此出现了错误

使用GridBagLayout正确渲染

PDFBox未加载内容

如何在Spring Java中从数据库列中获取JSON中的具体数据

如何使用Jackson将XML元素与值和属性一起封装

Arrays.hashcode(int[])为不同的元素提供相同的散列

Log4j与jdk21兼容吗?

是否在settings.xml中使用条件Maven镜像?

在Oracle db中,当我们提供字符串而不是数字时,比较是如何工作的?

活泼的一次判断成语,结果中等

Java类型推断:为什么要编译它?

升级版本后出现非法访问错误

转换为JSON字符串时,日期按天递减-Java

[Guice/MissingImplementation]:未绑定任何实现