概述
Java 的异常处理 是对代码性能 有着重要影响的因素
Java 的异常处理,有着天生优势 ,特别是在错误排查 方面的作用,很难找到合适的替代方案
用例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package me.zhongmingmao;import java.security.NoSuchAlgorithmException;public class UseCase { public static void main (String[] args) { String[] algorithms = {"SHA-128" , "SHA-192" }; String availableAlgorithm = null ; for (String algorithm : algorithms) { Digest md; try { md = Digest.of(algorithm); } catch (NoSuchAlgorithmException ex) { continue ; } try { md.digest("Hello, world!" .getBytes()); } catch (Exception ex) { System.getLogger("me.zhongmingmao" ) .log(System.Logger.Level.WARNING, algorithm + " does not work" , ex); continue ; } availableAlgorithm = algorithm; } if (availableAlgorithm != null ) { System.out.println(availableAlgorithm + " is available" ); } else { throw new RuntimeException ("No available hash algorithm" ); } } }
可恢复异常
NoSuchAlgorithmException
尝试捕获 并识别 这个异常,然后再从这个异常里恢复 过来,继续执行 代码
可恢复的异常处理 - 可以从异常里恢复过来,继续执行的异常处理
1 2 3 } catch (NoSuchAlgorithmException nsae) { }
只要 catch 语句能够捕获 并识别 这个异常,那么这个异常 的生命周期 就结束 了
catch 只需要知道异常的名字 ,而不需要知道异常的调用堆栈
可恢复异常 不使用异常的调用堆栈 ,是否可以不生成调用堆栈 ?
基于 Java 异常的性能基准测试 结果,生成异常的调用堆栈 是异常处理影响性能 的最主要因素
如果不需要生成调用堆栈,Java 异常的处理性能会有成百上千倍 的提升
不可恢复异常
RuntimeException
上述代码并没有捕获 和识别 ,该异常会直接导致程序的退出 ,并且把异常的信息和堆栈 打印出来
不可恢复的异常处理 - 导致了程序的中断 ,程序并不能从异常抛出 的地方恢复
调用堆栈 对于不可恢复异常 来说至关重要
可以从异常调用堆栈 的打印信息中,快速定位 到出问题的代码,降低了运维的成本
由于不可恢复异常中断 了程序的运行,因此它的开销是一次性 的
现实中,基本不会允许程序由于异常而中断退出 - 服务端 + 客户端
记录的调试信息
Exception
尝试捕获 并识别 这个异常,然后从异常里恢复 过来继续执行 代码 - 可恢复异常
同时,还在日志 里记录了该异常 ,异常的调试信息 ,即异常信息 和调用堆栈 ,也会被详细记录到日志中
典型的使用场景 - 程序可以恢复 ,但异常信息可以查询
1 2 3 4 5 } catch (Exception ex) { System.getLogger("me.zhongmingmao" ) .log(System.Logger.Level.WARNING, algorithm + " does not work" , ex); continue ; }
在异常捕获 的场景下 - 方法调用
该异常的记录方式 ,包括是否记录 me.zhongmingmao
该异常的记录地点 - System.getLogger()
该异常的严重程度 - Logger.Level
该异常的影响范围 - [algorithm] does not work
在异常生成 的场景下 - 方法实现
记录在案的调试信息 - 调用代码 + 实现代码
改进
共用错误码本身,并没有携带调试信息 ,为了能够快速定位 问题,需为共用错误码补充 调试信息
方法实现
使用异常 的形式补充了调用信息 ,包括问题描述 和调用堆栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static Returned<Digest> of (String algorithm) { return switch (algorithm) { case "SHA-256" -> new Returned .ReturnValue<>(new SHA256 ()); case "SHA-512" -> new Returned .ReturnValue<>(new SHA512 ()); case null -> { System.getLogger("me.zhongmingmao" ) .log( System.Logger.Level.WARNING, "No algorithm is specified" , new Throwable ("the calling stack" )); yield new Returned .ErrorCode<>(-1 ); } default -> { System.getLogger("me.zhongmingmao" ) .log( System.Logger.Level.INFO, "Unknown algorithm is specified %s" .formatted(algorithm), new Throwable ("the calling stack" )); yield new Returned .ErrorCode<>(-1024 ); } }; }
方法调用 1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) { Returned<Digest> returned = Digest.of("SHA-128" ); switch (returned) { case Returned.ReturnValue value -> {} case Returned.ErrorCode code -> { System.getLogger("me.zhongmingmao" ) .log( System.Logger.Level.INFO, "Failed to get instance of SHA-128, code: %d" .formatted(code.errorCode())); } } }
运行效果
类似于使用异常 处理,可以快速定位 问题的调试信息
对比
使用调试信息带来的性能损失 ,并不比使用异常的性能损失小多少
但日志记录既可以开启 ,也可以关闭
如果关闭 了日志,就不用再生成调试信息 了,对应的性能影响 也就消失了
在需要定位问题的时候,再启动日志
这样,可以把性能影响 控制在一个极小的范围 内
问题
共用错误码的解答
可恢复异常 能不能不生成调用堆栈 ?
可以,不开启日志 ,则不生成调用堆栈
不可恢复异常 还有存在必要 么?
没有,错误码方案的所有错误,都可以恢复
有没有快速定位 问题的替代方案 ?
有,开启日志 ,就能提供调试信息了
日志并不是唯一可以记录调试信息 的方式,可以使用更便捷的 JFR
错误码 的调试方式,更符合调试 的目的,只有需要调试 的时候,才会生成调试信息