当前位置: 首页 > 面试题库 >

异常对Java性能的影响是什么?

赖诚
2023-03-14
问题内容

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

传统观点以及许多Google的研究结果都表明,不应将异常逻辑用于Java中的常规程序流程。通常有两个原因,

它确实很慢-甚至比常规代码慢一个数量级(给出的原因各不相同),

这是混乱的,因为人们期望仅在异常代码中处理错误。
这个问题是关于#1的。

例如,此页面将Java异常处理描述为“非常慢”,并将这种缓慢与异常消息字符串的创建相关联-“然后,此字符串用于创建抛出的异常对象。这不是很快。” Java中的有效异常处理文章说:“其原因是由于异常处理的对象创建方面,因此使固有的异常抛出速度变慢”。另一个原因是堆栈跟踪的生成会降低堆栈跟踪的速度。

我的测试(在32位Linux上使用Java 1.6.0_07,Java HotSpot 10.0)表明异常处理并不比常规代码慢。我尝试在执行一些代码的循环中运行方法。在方法的最后,我使用一个布尔值来指示是返回还是throw。这样,实际处理是相同的。我尝试以不同的顺序运行这些方法并平均测试时间,以为可能是JVM变暖了。在我所有的测试中,投掷速度至少与返回速度一样快,甚至不快(高达3.1%)。我完全可以接受我的测试错误的可能性,但是我并没有看到代码样本,测试比较或最近一两年中显示Java异常处理的结果的任何方式慢。

导致我走这条路的是我需要使用的将异常作为常规控制逻辑的一部分的API。我想更正它们的用法,但现在可能无法。我是否必须赞扬他们的前瞻性思维?


问题答案:

取决于异常的实现方式。最简单的方法是使用setjmp和longjmp。这意味着将CPU的所有寄存器都写入堆栈(这已经花费了一些时间),并且可能需要创建一些其他数据…所有这些都已经在try语句中发生。throw语句需要展开堆栈并恢复所有寄存器的值(以及VM中可能的其他值)。因此try和throw都同样缓慢,而且也相当慢,但是,如果没有抛出异常,则在大多数情况下退出try块几乎不会花费任何时间(因为所有内容都放在堆栈中,如果该方法存在,则堆栈会自动清除)。

Sun和其他公司认识到,这可能不是最佳选择,并且随着时间的流逝,虚拟机当然会越来越快。还有另一种实现异常的方法,它可以使尝试本身快如闪电(实际上一般而言,尝试都不会发生任何事情-当类由VM加载时,所有需要发生的事情都已经完成了),并且抛出异常的速度也不太慢。我不知道哪个JVM使用这种更好的新技术…

…但是你是否使用Java编写,因此以后的代码只能在一个特定系统上的一个JVM上运行?由于它是否可以在任何其他平台或任何其他JVM版本(可能是任何其他供应商的版本)上运行,谁说他们也使用快速实现?快速的比慢的更复杂,并且在所有系统上都不容易实现。你想保持便携性吗?然后,不要依赖快速的异常。

在try块中执行的操作也有很大的不同。如果你打开一个try块,并且从不从该try块中调用任何方法,则try块将非常快,因为JIT实际上可以像简单的goto一样处理throw。如果抛出异常(它只需要跳转到catch处理程序),它既不需要保存堆栈状态,也不需要取消堆栈。但是,这不是你通常执行的操作。通常,你打开一个try块,然后调用一个可能引发异常的方法,对吗?即使你仅在方法中使用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放在同一方法(method3)中并全部抛出,结果将不会那么糟,但这是我不依赖的特殊JIT优化。即使使用此优化,抛出也仍然很慢。所以我不知道你要在这里做什么,但是绝对有比使用try / catch / throw更好的方法。



 类似资料:
  • 问题内容: 我有这个JavaWeb应用程序,它可以从电子表格上传成千上万的数据,该电子表格是从上到下按行读取的。我用来在服务器端显示应用程序当前正在读取的行。 -我知道要创建一个日志文件。实际上,我正在创建一个日志文件,同时在服务器提示符下显示日志。 还有其他方法可以在提示上打印当前数据? 问题答案: 它可能会影响您的应用程序性能。大小会因您所运行的硬件类型和主机上的负载而异。 可以将其转化为性能

  • 前言 HTTPS 在保护用户隐私,防止流量劫持方面发挥着非常关键的作用,但与此同时,HTTPS 也会降低用户访问速度,增加网站服务器的计算资源消耗。 本文主要介绍 https 对用户体验的影响。 HTTPS 对访问速度的影响 在介绍速度优化策略之前,先来看下 HTTPS 对速度有什么影响。影响主要来自两方面: 协议交互所增加的网络 RTT(round trip time)。 加解密相关的计算耗时。

  • 使用时,是否有需要考虑的性能影响? 我正在编写一个从目录检索文件的查询,这就是查询: 那么,在决定进行这样的转换时,是否应该考虑某种性能影响--还是只在处理大量文件时才考虑?这是一个可以忽略不计的转换吗?

  • 他们看到的Watson语音到文本服务器最近的问题是,无法在单个实例中运行持续时间大于10分钟的音频文件,其次,当运行语音到文本的文件小于10分钟时,有时与Watson服务器的TCP连接丢失。 他们基本上是在python的Watson服务器上使用web套接字,并且想了解对于持续时间较长的音频文件(例如,我们的会议持续到3小时),什么是运行语音到文本的最佳方式。为web套接字上的连接丢失配置瓶颈的最佳

  • 我对Spring请求映射的内部工作很好奇。在类级别使用requestmapping注释是否会加快解析请求的控制器? 在Spring Boot中@RequestMapping如何在内部工作?-阅读这个答案和类似的答案。控制器和URL是否在初始启动期间映射并存储在注册表中?因为我在启动Spring Boot应用程序时发现了这些日志跟踪。 控制器+方法和URL是最初在启动期间映射的,还是每次为请求迭代?

  • 为什么。NET 4.0中C#方法的及时编译顺序会影响它们的执行速度?例如,考虑两种等效的方法: 唯一的区别是引入了局部变量,这会影响生成的汇编代码和循环性能。为什么会这样,这本身就是一个问题。 可能更奇怪的是,在x86(而不是x64)上,调用方法的顺序对性能有大约20%的影响。调用如下方法。。。 ...单线测试速度更快。(使用x86版本配置编译,确保启用了“优化代码”设置,并从VS2010外部运行