写点什么

Java 王者修炼手册【基础篇 - 异常机制】:Java 异常的底层逻辑

作者:DonaldCen
  • 2025-12-05
    广东
  • 本文字数:2923 字

    阅读完需:约 10 分钟

Java 王者修炼手册【基础篇-异常机制】:Java异常的底层逻辑

大家好,我是程序员强子。


写代码就像走夜路 —— 你永远不知道什么时候会踩到 坑:数组越界、文件找不到、网络断了。。这些突发状况如果处理不好,程序就会直接崩溃(经常闪退,或者界面显示了很多所谓的【乱码】)。


异常机制就是 Java 给我们留的体面,不至于一点小问题就闪退或者报错误:能帮我们识别哪些错误是致命的、哪些问题是可修复的,再用标准化流程化的方式处理了,让程序在遇到问题时更【体面】。


今天我们来盘一下今天练哪个英雄,准备练到什么程度,掌握哪些技巧:


  • 异常的层次结构:先搞懂 【敌人】的分类

  • Error(错误):无法解决的致命问题

  • Exception(异常):可以处理的运行时问题

  • 异常捕获:四套 【处理方案】按需选

  • try-catch:最基础的 【捕获 + 处理】

  • try-catch-finally:处理后必须 【收尾】

  • try-finally:只 【收尾】 不处理异常

  • try-with-resource:自动 【收尾】(Java 7+)

  • 异常实践: 【避坑指南】 让代码更专业

  • 不要捕获 Throwable 类

  • 不要用异常控制程序流程

  • 深入理解异常:底层机制和性能影响

  • JVM 是如何处理异常的?

  • 异常会影响性能吗?为什么?

  • 异常表(Exception Table)


话不多说,开始今天英雄练习场~

异常的层次结构

Throwable 是所有错误和异常的顶层父类,分为两大子类


  • Error(错误)

  • JVM 无法处理的严重问题(如 OutOfMemoryError、StackOverflowError),属于程序无法恢复的致命错误,通常无需捕获,应通过优化代码避免。

  • 是 【服务器级别的致命故障】—— 比如玩到一半突然闪退、服务器维护,这种问题不是你操作能解决的,只能等官方修复

  • Exception(异常):程序可以处理的异常,分为

  • Checked Exception(受检异常):编译期强制检查,必须显式捕获或声明抛出(如 IOException、ClassNotFoundException)

  • Unchecked Exception(非受检异常):继承自 RuntimeException,编译期不强制处理(如 NullPointerException、IndexOutOfBoundsException),通常由程序逻辑错误导致

  • 是 【游戏内的可处理失误】—— 比如技能放歪了、被野怪拍死、装备买错了。这些虽然麻烦,但能补救:技能空了可以等 CD,死了可以复活,装备买错了可以卖掉


简单说:Throwable 是所有【麻烦】 的总名,Error 是 【救不了的大麻烦】,Exception 是 【能解决的小麻烦】。

异常捕获

try-catch

try {    // 可能抛出异常的代码(比如读取文件)    FileReader fr = new FileReader("test.txt");} catch (FileNotFoundException e) {    // 捕获到异常时的处理逻辑(比如提示"文件不存在")    System.out.println("错误:找不到指定文件");}
复制代码


尝试执行风险代码,一旦抛出异常就被对应的 catch 块捕获,避免程序崩溃

try-catch-finally

FileReader fr = null;try {    fr = new FileReader("test.txt");} catch (FileNotFoundException e) {    e.printStackTrace();} finally {    // 无论是否发生异常,这里的代码一定会执行(除非JVM退出)    if (fr != null) {        try {            fr.close(); // 关闭文件流,释放资源        } catch (IOException e) {            e.printStackTrace();        }    }}
复制代码


需要释放资源(文件流、数据库连接等)时,finally 块能保证资源被清理

try-finally

try {    // 执行代码,异常会直接抛出    riskyOperation();} finally {    // 无论是否异常,必须执行的收尾操作    cleanUpResources();}
复制代码


不处理异常(异常会向上传递),但确保资源释放。

try-with-resource

// 资源会自动关闭无需手动写finallytry (FileReader fr = new FileReader("test.txt")) {    // 使用资源的代码    int data = fr.read();} catch (IOException e) {    e.printStackTrace();}
复制代码


简化代码,避免因忘记关闭资源导致的内存泄漏

异常最佳实践

  • 只针对异常情况用异常,不要用它控制正常流程,否则会降低代码可读性

  • 用 finally 或 try-with-resource 清理资源,简洁,也能避免忘记关闭而导致内存泄漏

  • 尽量使用标准异常,比如处理文件异常时,先捕获 FileNotFoundException,再捕获更通用的 IOException,而不是直接捕获 Exception

  • 不要捕获 Throwable 类,Throwable 包含 Error 和 Exception,捕获它会把 JVM 的致命错误也 "吞掉",导致问题难以排查

  • 不要忽略异常,不要写空的 catch 块(catch (Exception e) {}),这会隐藏问题,让 bug 难以定位

  • 不要用异常控制程序流程,用 if-else 判断正常逻辑,用异常处理意外情况

  • 不要在 finally 块中使用 return

  • finally 块的 return 会覆盖 try 或 catch 中的返回值,导致逻辑混乱

  • 比如 try 中返回 66,finally 中返回 yyds,在 finally 块中使用 return 后最终结果是 yyds

深入理解异常

JVM 处理异常的机制

  1. 异常触发

  2. 程序执行时发生异常,JVM 生成异常对象(包含类型、消息、堆栈信息等等)

  3. 就像你在打红 BUFF 时,突然从草里冲出 3 个敌人(触发异常)—— 此时你的操作会立刻暂停,脑子里第一反应是 “你们真是老六,真的服了,出事了!”。

  4. 栈帧回溯

  5. JVM 从当前栈帧向上遍历调用栈,查找匹配的 catch 块(按异常类型匹配)

  6. 被蹲后,你会先喊身边的队友(比如辅助):“救我!”;如果辅助没反应,再喊中路、打野(逐层向上求助)

  7. 异常触发后,JVM 会从当前方法(栈顶帧)开始,向上遍历调用栈(比如 methodC()→调用它的 methodB()→再上层的 methodA()),寻找能处理这个异常的 catch 块,就像 “逐层问队友能不能救”。

  8. 异常处理

  9. 找到匹配的 catch 块后,执行其中逻辑;若未找到,继续向上抛,直至 JVM 顶层(此时打印堆栈并终止程序)

  10. 如果打野刚好在附近,过来把敌人打退(处理了异常),你们继续推进;如果没人能救,你们就团灭 GG(程序崩溃)。

  11. 堆栈信息生成

  12. 异常对象创建时会记录当前调用栈(通过 fillInStackTrace() 方法),这是耗时的主要原因之一。

  13. 团战后,系统会生成战报:“XX 在红 BUFF 处被 XX 击杀,当时队友 XX 在中路、XX 在发育路”(记录完整过程)

  14. 异常对象创建时,会生成堆栈信息(StackTrace),记录异常发生的位置(哪个类、哪个方法、第几行),以及调用栈里的所有方法(谁调用了谁),就像 【战报】 一样,方便开发者复盘问题

  15. 记录的格外详细,所以非常耗时

异常会影响性能吗?为什么?

  • 会,而且可能很显著

  • 创建异常对象时,JVM 需要收集 "调用栈信息"(每个方法的类名、方法名、行号等),这个过程需要遍历整个调用栈,非常耗时

异常表(Exception Table)

异常表是 JVM 字节码层面用于记录异常捕获和处理信息的数据结构,每个方法的字节码中都可能包含一个异常表,表中每一条记录(称为 【异常处理项】)包含以下信息:


  • start_pc:try 块的起始字节码偏移量(即 try 块从哪行字节码开始)。

  • end_pc:try 块的结束字节码偏移量(try 块到哪行字节码结束,不含此偏移量)。

  • handler_pc:catch 块的起始字节码偏移量(异常发生后跳转到这里执行)。

  • catch_type:需要捕获的异常类型(指向常量池中的类引用,若为 null 表示处理所有异常,对应 finally 逻辑)


简单点说:


异常表是 【险情处理指南】,JVM 照着指南安排救援。就像地图上标记的 【危险区域】(try 块)和 【救援点】(catch/finally 块)。


  1. 每个标记记着:危险区从哪到哪(start_pc/end_pc)、

  2. 出事了往哪个救援点跑(handler_pc)、

  3. 能处理哪种险情(catch_type,比如 空指针 算术错误 等等)

发布于: 刚刚阅读数: 2
用户头像

DonaldCen

关注

有个性,没签名 2019-01-13 加入

跟我在峡谷学Java 公众号:程序员悟空的宝藏乐园

评论

发布
暂无评论
Java 王者修炼手册【基础篇-异常机制】:Java异常的底层逻辑_异常底层原理_DonaldCen_InfoQ写作社区