Java 异常:异常机制对程序性能的影响

前言

本文主要分析一下异常机制对Java程序性能的影响,从而警惕对异常机制的滥用。

核心:异常机制的设计初衷是处理程序错误,而非控制正常流程。滥用异常会显著降低系统性能。

异常可能慢的原因

异常处理主要可以分为两大部分:异常创建、异常抛出和异常捕获

构造异常对象的昂贵开销 (new Exception()):

  • fillInStackTrace() 方法 :这是罪魁祸首。当你创建一个异常对象(如 new Exception())时,其构造函数默认会调用 fillInStackTrace() 这个 native 方法。
  • 快照收集:JVM 需要遍历当前线程的调用栈,从当前方法一直追溯到线程的入口。
  • 信息记录:它必须抓取每一层栈帧的类名、方法名、文件名、源代码行号等信息,并封装在对象中。

抛出与捕获异常的流程开销 (throw + catch):

即使异常对象已经创建好,执行 throwcatch 的过程依然比普通的条件判断慢得多:

  • 异常表查找:JVM 需要在当前栈帧中查找异常表,这是一个线性扫描过程(虽然异常表通常很短,但仍有开销)。
  • 栈回溯 :如果当前方法没有 catch 处理,JVM 需要弹出当前栈帧,回到调用者,继续查找。这涉及复杂的栈帧操作和上下文恢复。
  • 上下文切换与清理 :跳转到 catch 块时,JVM 需要清理操作数栈,重新设置程序计数器(PC),这比简单的 if (e != null) 分支预测要复杂得多。

基准比较

基准代码比较如下:

csharp 复制代码
package com.exception;  
  
import java.util.ArrayList;  
import java.util.List;  
  
public class ExceptionTest {  
  
    private int testTimes;  
  
    // 定义一个 volatile 黑洞,防止 JIT 编译器优化掉测试对象的创建和使用  
    // volatile 保证每次写入都必须刷回主存,防止编译器认为赋值是无意义的死代码  
    private static volatile Object BLACK_HOLE;  
  
    public ExceptionTest(int testTimes) {  
        this.testTimes = testTimes;  
    }  
      
    public void newObject() {  
        long l = System.nanoTime();  
        List<Object> list = new ArrayList<>(testTimes);  
        Object obj = null;  
        for (int i = 0; i < testTimes; i++) {  
            obj = new Object();  
            list.add(obj);  
        }  
        // 必须使用对象,否则 JVM 会优化掉整个循环内的 new 操作  
        BLACK_HOLE = obj;  
        System.out.println("1. 建立普通对象:" + (System.nanoTime() - l) + " ns");  
    }  
  
    public void newException() {  
        List<Exception> list = new ArrayList<>(testTimes);  
        long l = System.nanoTime();  
        for (int i = 0; i < testTimes; i++) {  
            // 仅创建,不抛出  
            list.add(new Exception());  
        }  
        BLACK_HOLE = list; // 强制保留所有对象,防止 GC 或优化  
        System.out.println("2. 仅建立异常对象:" + (System.nanoTime() - l) + " ns");  
    }  
  
    public void catchException() {  
        List<Exception> list = new ArrayList<>(testTimes);  
        long l = System.nanoTime();  
        for (int i = 0; i < testTimes; i++) {  
            try {  
                // 抛出异常  
                throw new Exception();  
            } catch (Exception e) {  
                // 关键:将每个捕获的对象都存起来  
                // 这逼迫 JVM 必须完整执行 new 和 throw 流程,不能优化掉任何一个  
                list.add(e);  
            }  
        }  
        BLACK_HOLE = list; // 强制保留所有对象  
        System.out.println("3. 建立、抛出并接住异常对象:" + (System.nanoTime() - l) + " ns");  
    }  
  
    public static void main(String[] args) {  
        // 建议稍微增加次数,或者在 main 方法外层套一个循环预热 JVM,  
        // 否则第一次运行的数据可能会包含类加载和解释执行的时间,不够准确  
        ExceptionTest test = new ExceptionTest(100_000);  
        // 预热:让 JIT 编译器介入,把代码编译成机器码  
        System.out.println("--- 预热中 ---");  
        for(int i=0; i<10000; i++) {  
            try { throw new Exception(); } catch (Exception e) {}  
        }  
  
        // 稍微延迟,确保编译完成  
        try { Thread.sleep(100); } catch (InterruptedException e) {}  
          
        System.out.println("=== 开始性能测试 ===");  
        test.newObject();  
        test.newException();  
        test.catchException();  
    }  
}

本地执行结果如下:

markdown 复制代码
--- 预热中 ---
=== 开始性能测试 ===
1. 建立普通对象:7371385 ns
2. 仅建立异常对象:105856936 ns
3. 建立、抛出并接住异常对象:147632209 ns

建立一个异常对象,是建立一个普通Object耗时的约10倍,而抛出、接住一个异常对象,所花费时间大约是建立异常对象的1.5倍。

那占用时间的"大头":抛出、接住异常,系统到底做了什么事情?大致做如下内容:

  • 检查栈顶异常对象类型
  • 把异常对象的引用出栈
  • 搜索异常表,找到匹配的异常handler
  • 重置PC寄存器状态
  • 清理操作栈
  • 把异常对象的引用入栈
  • 把异常方法的栈帧逐个出栈(这里的栈是VM栈)
  • 残忍地终止掉当前线程。

详情请参考这篇文章:www.iteye.com/blog/icyfen...

相关推荐
SimonKing2 小时前
被AI编程折磨的苦不堪言:一边喊真香,一边想砸键盘
java·后端·程序员
xhxxx2 小时前
RAG实战-基于 Milvus 和 LangChain 实现的天龙八部阅读助手
后端·langchain·aigc
猹叉叉(学习版)2 小时前
【ASP.NET CORE】 7. Identity标识框架
笔记·后端·c#·asp.net·.netcore
白衣鸽子2 小时前
Java 异常:异常类型和异常捕获原理
后端
zjjsctcdl2 小时前
Spring之FactoryBean详解
java·后端·spring
翘着二郎腿的程序猿2 小时前
SpringBoot集成Knife4j/Swagger:接口文档自动生成,告别手写API文档
java·spring boot·后端
小鸡脚来咯2 小时前
Spring Boot 常见面试题汇总
java·spring boot·后端
小旭95272 小时前
【超详细】Spring 核心知识点全解析(IOC+AOP)
java·后端·spring·maven·intellij-idea
李白的粉2 小时前
基于springboot的阿博图书馆管理系统
java·spring boot·后端·毕业设计·课程设计·源代码·图书馆管理系统