当前位置: 首页 > news >正文

第十一章 用Java实现JVM之异常处理

用Java实现JVM目录

第零章 用Java实现JVM之随便说点什么
第一章 用Java实现JVM之JVM的准备知识
第二章 用Java实现JVM之命令行工具
第三章 用Java实现JVM之查找Class文件
第四章 用Java实现JVM之解析class文件
第五章 用Java实现JVM之运行时数据区
第六章 用Java实现JVM之指令集和解释器
第七章 用Java实现JVM之类和对象
第八章 用Java实现JVM之方法调用和返回
第九章 用Java实现JVM之数组和字符串
第十章 用Java实现JVM之本地方法调用
第十一章 用Java实现JVM之异常处理
第十二章 用Java实现JVM之结束


文章目录

  • 用Java实现JVM目录
  • 前言
  • 异常
      • 一、错误(Error)
      • 二、异常(Exception)
          • 1. **受检异常(Checked Exception)**
          • 2. **非受检异常(Unchecked Exception)**
  • 代码实现
          • 异常处理表
          • `athrow`指令
          • 异常打印
  • 测试
  • 总结


前言

    上一篇我们已经实现了本地方法,今天开启新的征程,继续往下。聚焦于异常处理

异常

    从大的方向来看,Java 在运行过程中可能遇到的问题,主要可以划分为两大类:错误(Error)异常(Exception)。这两者虽然在表现上可能都是程序运行中断,但它们的含义、产生原因、处理方式却截然不同。


一、错误(Error)

    错误 通常指的是一些 严重的系统级问题,这些问题非程序本身能处理,一旦出现,基本就意味着 JVM 自身出了问题,程序无法继续运行。例如:

  • OutOfMemoryError:内存溢出,JVM 无法为对象分配内存

  • StackOverflowError:栈空间溢出,通常是无限递归调用导致

  • VirtualMachineError:虚拟机内部错误,如 JIT 编译器 崩溃

    这类错误并不推荐用 try-catch 来捕获,也不建议也不可能完全恢复,更像是一种“致命信号”:程序可能已经无法继续安全运行。通常我们只能记录日志,或通知运维重启服务,根本原因往往是代码设计或配置不当,或者系统资源不足


二、异常(Exception)

    相比之下,异常 则是 应用级别的问题,指程序运行过程中可预期、可以被捕获和处理的问题。它们一般不会导致整个 JVM 崩溃,但需要程序员显式处理。例如:

  • NullPointerException:空指针异常。

  • IOException:输入输出异常,文件或网络读取失败。

  • SQLException:数据库操作时出错。

    Java 中,异常又被进一步分为两类:

1. 受检异常(Checked Exception)
  • 特点:必须显示处理(编译期检查),否则编译器报错
  • 常见场景:文件操作、数据库连接、网络通信等
  • 例如:IOExceptionSQLException
  • 解决方式:用 try-catch 捕获,或者用 throws 抛出
2. 非受检异常(Unchecked Exception)
  • 特点:运行时才出现,不强制处理(编译期不报错)
  • 通常是程序逻辑上的错误,如访问空对象、数组越界等
  • 例如:NullPointerExceptionIndexOutOfBoundsException
  • 解决方式:可以用 try-catch 捕获,也可以通过加强逻辑判断来避免

代码实现

    梳理完 Java 的异常体系后,回到现实的问题,JVM 要如何处理异常?在日常开发出,我们可能抛出来一个或多个异常,出现异常后也会打印出来报错的行数。异常发生时程序需要做什么,不妨再深入想一想:

  • 当异常抛出时,它具体从哪里“冒出来”?
  • 它是怎么被虚拟机捕获的?
  • 处理异常的代码又是怎么被找到的?

    回想平时用到的 try-catch,它本质上就是一段告诉虚拟机: “如果在这段代码里出现了某种异常,跳转到这里处理它”。但虚拟机又是如何知道这其中的对应关系?经过前面一系列的洗礼,一旦出现多个XX,基本上都会存在一张表。异常也不例外。字节码文件里藏着一张“异常表”(Exception Table),这张表详细记录了每个 try-catch 的起止位置,以及能处理哪些异常类型。当执行到某条指令时,如果抛出了异常,虚拟机会查这张表,看看当前指令是否在某个 try 区间内;如果在,再根据异常类型找到对应的catch 块。如果找到,就调整程序计数器,跳转到对应的 catch 代码继续执行。如果找不到,说明当前方法不能处理,虚拟机就会弹出当前调用栈,回到调用它的方法,重复这个查找过程。直到找到处理代码,或者调用栈空了,异常传递无果,虚拟机才会把异常打印出来

    这样一来,异常的捕获与处理就像在字节码里有一个“地图”,告诉虚拟机:“遇到异常,去这里走。”同时,为了让错误提示更友好,定位到具体的源码行号,字节码中还有专门的“行号表”(LineNumberTable),将字节码指令的偏移量对应到源码的行号。当异常发生,虚拟机结合异常抛出的指令地址和行号表,就能告诉你报错的代码行在哪里。因为源代码中,很多东西是给人看的,不是给虚拟机看的。所以在编译的过程中会过滤掉这些,导致字节码的行号和源代码的行号对不上


这就是 JVM 规范 对异常处理的基本设计:

  • 异常表(Exception Table)
  • 行号表(LineNumberTable)
  • 程序计数器(PC)用于定位当前执行指令
  • 调用栈帧的弹出与跳转机制
+-----------+----------+-------------+------------+
| start_pc  | end_pc   | handler_pc  | catch_type |
+-----------+----------+-------------+------------+
|    0      |   20     |     30      |  NumberFormatException |
+-----------+----------+-------------+------------+
异常处理表

    巴拉了这么多,先把字节码中的 异常处理表 解析出来再说。JMethod添加如下代码:

	private List<JMethodExceptionTable> catchExceptionTable;private List<JClassRef> throwExceptionTable;@Overrideprotected void newDescInfo(AttributeInfo attr, JClass jClass, List<ConstantPool> constantPool) {if (attr instanceof Code) {Code code = (Code) attr;this.setCode(code.getInstructionCode());this.setMaxStack(code.getMaxStack());this.setMaxLocals(code.getMaxLocals());resolveExceptionTable(code, constantPool);resolveAttrs(code, constantPool);} else if (attr instanceof Exceptions) {this.resolveExceptions((Exceptions) attr, constantPool);} else if (attr instanceof RuntimeVisibleParameterAnnotations) {RuntimeVisibleParameterAnnotations paramAnno = (RuntimeVisibleParameterAnnotations) attr;this.paramAnnos = paramAnno.getByteCodes();}}private void resolveExceptions(Exceptions ex, List<ConstantPool> constantPool) {List<Integer> indexList = ex.getExceptionIndexTable();if (CollectionUtils.isNotEmpty(indexList)) {for (Integer index : indexList) {JClassRef jClassRef = (JClassRef) constantPool.get(index).getVal();this.throwExceptionTable.add(jClassRef);}}}
athrow指令

    代码上的功能,对应到虚拟机都是由指令来完成。异常也是一样的。抛出异常的指令为athrow。整个指令的功能就是根据栈顶的异常对象,去异常表进行匹配。RefInstruction#execute()改动如下:

	case ATHROW: {//抛出异常信息JObject ref = popOperandStackRefVal();if (ref == null) {throw new NullPointerException();}while (!jThread.getJvmStack().isEmpty()) {//回到上一个指令int pc = jThread.getNextPc().get() - 1;int handlerPC = getJMethod().findExceptionHandler(ref.getJClass(), pc);if (handlerPC != -1) {getStackFrame().getOperandStack().getStack().clear();offerIncrement(handlerPC - 1);break;}jThread.getJvmStack().pop();}if (jThread.getJvmStack().isEmpty()) {JField jField = ref.getJClass().getJField("detailMessage", "Ljava/lang/String;");String msg = new String((char[]) ((JObject) (((JObject) ref.getField(jField.getSlotId()).getVal())).getFields()[0].getVal()).getData());System.err.println(ref.getJClass().getClassName() + ": " + msg);end();}break;}
异常打印

    异常信息的打印还需要本地方法支持。新增JThrowableNativeRegistry类,代码如下:

public class JThrowableNativeRegistry extends JNativeRegistry {private static final JThrowableNativeRegistry instance = new JThrowableNativeRegistry();static {registry("java/lang/Throwable", "fillInStackTrace", "(I)Ljava/lang/Throwable;", JThrowableNativeRegistry::fillInStackTrace);}protected static void fillInStackTrace(JThread jThread) {JObject ref = jThread.getJvmStack().getTop().getLocalVars().getRefVal(0);jThread.getJvmStack().pushOperandStackRefVal(ref);ref.setExtra(createStackTraceElements(ref, jThread));}protected static JStackTraceElement[] createStackTraceElements(JObject exJObject, JThread jThread) {int skip = distanceToObject(exJObject.getJClass()) + 2;StackFrame[] sfs = new StackFrame[jThread.getJvmStack().size() - skip];JStackTraceElement[] jstes = new JStackTraceElement[sfs.length];for (int i = 0; i < sfs.length; i++) {StackFrame sf = jThread.getJvmStack().elementAt(sfs.length - i - 1);JClass jc = sf.getJClass();JMethod jm = sf.getJMethod();JStackTraceElement ste = new JStackTraceElement(jc.getSourceFile(), jc.getClassName(), jm.getName(), jm.getLineNumber(sf.getReturnAddress().get() - 1));jstes[i] = ste;}return jstes;}private static int distanceToObject(JClass exJClass) {int distance = 0;for (JClass jc = exJClass.getSuperClass(); jc != null; jc = jc.getSuperClass()) {distance++;}return distance;}public static JThrowableNativeRegistry getInstance() {return instance;}/*** 虚拟机栈信息*/@Getter@Setter@NoArgsConstructor@AllArgsConstructorstaticclass JStackTraceElement {private String fileName;private String className;private String methodName;private Integer lineNumber;}
}

测试

    ok,接下来进入测试环节,验证今天的努力成果。新增ParseIntTest类,代码如下:

public class ParseIntTest {public static void main(String[] args) {foo(args);}private static void foo(String[] args) {try {bar(args);} catch (NumberFormatException e) {System.out.println(e.getMessage());}}private static void bar(String[] args) {if (args.length == 0) {throw new IndexOutOfBoundsException("no args!");}int x = Integer.parseInt(args[0]);System.out.println(x);}
}

在添加一个测试类,ExTest代码如下:

public class ExTest {public static void main(String[] args) {CmdCommand cmdCommand = new CmdCommand();cmdCommand.parseCmd(args);}
}

idea增加两个启动类,ExTest-normal配置如下:

-Xjre "D:\Oracle\Java\jdk1.8.0_281\jre" -cp "Z:\code\jjvm\ch10\target\test-classes" com.hqd.jjvm.ex.ParseIntTest aa bb

在这里插入图片描述

idea增加两个启动类,ExTest-ex配置如下:

-Xjre "D:\Oracle\Java\jdk1.8.0_281\jre" -cp "Z:\code\jjvm\ch10\target\test-classes" com.hqd.jjvm.ex.ParseIntTest

在这里插入图片描述

测试结果如下:

在这里插入图片描述

在这里插入图片描述

有参数的顺利打印出来参数,无参数的抛出了异常


总结

    今天主要讲述异常处理,整个 JVM 的实现进度已经进入尾声了。。。

http://www.xdnf.cn/news/1169857.html

相关文章:

  • 使用 Ansys Fluent 软件参数化工作流程对搅拌罐中的稳态涡流进行仿真
  • 质量即服务:从测试策略到平台运营的全链路作战手册
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(7):自動詞 & 他動詞
  • es6中的symbol基础知识
  • Lua语言
  • web登录页面
  • Elasticsearch Java 8.x 的聚合 API 及子聚合的用法
  • 外网访问内部私有局域网方案,解决运营商只分配内网IP不给公网IP问题
  • iOS加固工具有哪些?从零源码到深度混淆的全景解读
  • DearMom以“新生儿安全系统”重塑婴儿车价值,揽获CBME双项大奖
  • vue2.0 + elementui + i18n:实现多语言功能
  • fuse低代码工作流平台概述【已开源】-自研
  • Java中关于线程池的解析
  • Qt 事件处理机制深入剖析
  • 厌氧菌数据挖掘可行性评估报告
  • 深入理解 Qt 中的 QImage 与 QPixmap:底层机制、差异、优化策略全解析
  • PyQt5在Pycharm上的环境搭建 -- Qt Designer + Pyuic + Pyrcc组合,大幅提升GUI开发效率
  • stm32 智能小车
  • [2025CVPR-小目标检测方向]基于特征信息驱动位置高斯分布估计微小目标检测模型
  • AI视频-剧本篇学习笔记
  • LeetCode 633.平方数之和
  • Leetcode力扣解题记录--第73题(矩阵置零)
  • RabbitMQ-交换机(Exchange)
  • 【大模型记忆实战Demo】基于SpringAIAlibaba通过内存和Redis两种方式实现多轮记忆对话
  • Arraylist与LinkedList区别
  • STM32-SPI全双工同步通信
  • LWIP学习记录2——MAC内核
  • mybatis多对一一对多的关联及拼接操作以及缓存处理
  • 【学习路线】Python全栈开发攻略:从编程入门到AI应用实战
  • Custom SRP - Draw Calls