Java异常与正确处理方式

本期话题:聊聊Java的异常吧,Exception和Error?运行时异常与一般异常?

General Knowledge or Common Sense

  • Exception和Error继承了Throwable,可以被throw或catch,是异常的处理机制的基本组成类型
  • Exception是程序正常运行中,可以预料的意外,应当被捕获并进行处理
  • Error是非正常情况,无需捕获和处理,比如OutOfMemoryError等,由JVM抛出和处理
  • Exception分类
    • 必检异常[checked]:编译期会检查,必须显示的捕获和处理
    • 非必检异常[unchecked]:运行时异常,通常通过编码避免逻辑错误
Throwable、Exception、Error的设计和分类 + 自定义异常

异常体系

小作业:NoClassDefFoundError vs ClassNotFoundException

  • NoClassDefFoundError:

JVM或者ClassLoader在加载一个class(通过方法调用或者创建实例)的时候,目标class没有被找到。编译期间可以被编译,但是运行时找不到。可能的原因,比如OSGI文件里的lib包,没有在导出插件时被一起导出。

/**
 * Thrown if the Java Virtual Machine or a <code>ClassLoader</code> instance
 * tries to load in the definition of a class (as part of a normal method call
 * or as part of creating a new instance using the <code>new</code> expression)
 * and no definition of the class could be found.
 * <p>
 * The searched-for class definition existed when the currently
 * executing class was compiled, but the definition can no longer be
 * found.
 *
 * @author  unascribed
 * @since   JDK1.0
 */
  • ClassNotFoundException

通过Class.ForName或者ClassLoader.findSystemClass、CLassLoader.loadClass通过类的QualifiedName加载失败,常见于XML文件注册注入类对象时发生

/**
 * Thrown when an application tries to load in a class through its
 * string name using:
 * <ul>
 * <li>The <code>forName</code> method in class <code>Class</code>.
 * <li>The <code>findSystemClass</code> method in class
 *     <code>ClassLoader</code> .
 * <li>The <code>loadClass</code> method in class <code>ClassLoader</code>.
 * </ul>
 * <p>
 * but no definition for the class with the specified name could be found.
 *
 * <p>As of release 1.4, this exception has been retrofitted to conform to
 * the general purpose exception-chaining mechanism.  The "optional exception
 * that was raised while loading the class" that may be provided at
 * construction time and accessed via the {@link #getException()} method is
 * now known as the <i>cause</i>, and may be accessed via the {@link
 * Throwable#getCause()} method, as well as the aforementioned "legacy method."
 *
 * @author  unascribed
 * @see     java.lang.Class#forName(java.lang.String)
 * @see     java.lang.ClassLoader#findSystemClass(java.lang.String)
 * @see     java.lang.ClassLoader#loadClass(java.lang.String, boolean)
 * @since   JDK1.0
 */
How to use code handle Exception?
  • try-catch-finally
  • try-with-resources
  • multiple catch
//扩展了AotuCloseable或者Closeable的对象
try (BufferedReader br = new BufferedReader();
     BufferedWriter writer = new BufferedWriter()) {// Try-with-resources
// do something
catch ( IOException | XEception e) {// Multiple catch
   // Handle it
} 

Something important
  • 捕获特定异常,避免直接捕获Exception

  • 除非你非常确定,否则不要捕获Throwable或者Error

  • 不要生吞(swallow)异常,异常可以帮助诊断问题

  • 避免直接使用e.printStackTrace()

    1. printStackTrace输出位置不一定:API描述"Print this throwable and its backtrace to the standard error stream",也是就是可以通过指定System.err,重定向输出流的位置,如果输入到linux的black hole…嗯…启动命令类似如下:
    nohup ./app.sh >/dev/null 2>&1 &
    

    关于Black Hole可以看下Null Device 介绍

    1. 在printStackTrace没有重定向的时候,输出到console是非常低效的,同步IO操作会导致线程卡死
  • throw early

    public void readPreferences(String filename) {
    //提前校验非空的内容,减少异常的堆栈
    Objects. requireNonNull(filename);
    //...perform other operations... 
    InputStream in = new FileInputStream(filename);
     //...read the preferences file...
    }
    
  • catch late
    调用方或者更高的层面有清晰的(业务)逻辑,处理异常更加适合,因此底层代码可以继续向上抛出异常

  • 自定义异常

    1. 是否设置为Checked Exception:必检异常是为了把程序从异常情况恢复,除此之外使用RuntimeException及其子类,避免代码出现太多的try-catch块
    2. wrap msg:通过自定义异常,将exception的msg封装为可读,脱敏的自然语言,真正的异常记录日志,提高程序的安全性,避免暴露细节
性能为王
  • try-catch代码会产生额外性能开销,影响JVM对代码的优化,因此需要避免一个大的try包住大段代码;

  • 避免使用try-catch的异常控制代码流程

    //这是一个非常典型的反例,也是一个误区.
    /**
     * 处理业务消息
     * @param message 要处理的消息
     */
      public void processMessage(Message<String> message) {
        try{
            // 处理消息验证
            // 处理消息解析
            // 处理消息入库
        }catch(ValidateException e ){
            // 验证失败
        }catch(ParseException e ){
            // 解析失败
        }catch(PersistException e ){
            // 入库失败
        }
      }
      
     //修改后的逻辑
     /**
      * 处理业务消息
      * @param message 要处理的消息
      */
      public void processMessage(Message<String> message) {
        // 处理消息验证
        if(!message.isValud()){
            MessageLogService.log("消息校验失败"+message.errors())
            return ;
        }
        // 处理消息解析
        if(!message.parse()){
            MessageLogService.log("消息解析失败"+message.errors())
            return ;
        }
         // TODO ....
      }
      
    
  • Java每实例化一个Exception都会对当时的栈进行快照,如果发生的异常非常频繁,性能会有损耗。当服务的吞吐下降时,检查发生最频繁的Exception也是一种思路

延展小话题:异常的日志

分布式环境下出现异常后,如果快速定位问题呢。一般通过ELK可以搭建日志平台,收集日志。增加traceid/requestId也可以串联
多线程日志:通过MDC/NDC功能串联日志,或者traceid和requestid

抛一个问题:Checked Exception对Lambda/Stream造成的麻烦(目前体会尚欠,后续更新)
lambda的程序,调用一个抛出检查异常的方法,现在语言层面并没有流畅处理机制,往往需要自定义functional interface
再抛一个问题:Reactive Stream对异常处理的最佳实践(目前体会尚欠,后续更新)

1.异常:这种情况下的异常,可以通过完善任务重试机制,当执行异常时,保存当前任务信息加入重试队列。重试的策略根据业务需要决定,当达到重试上限依然无法成功,记录任务执行失败,同时发出告警。
2.日志:类比消息中间件,处在不同线程之间的同一任务,简单高效一点的做法可能是用traceId/requestId串联。有些日志系统本身支持MDC/NDC功能,可以串联相关联的日志。

全局异常Spring MVC的方式就很实用;

Checked Exception 不兼容 functional 编程,如果你写过 Lambda/Stream 代码,相信深有体会。这段话可以详细剖析下吗?

参考文档:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值