# exception

back

TIP

java中的异常的超类是java.lang.Throwable(后文省略为Throwable),它有两个比较重要的子类,java.lang.Exception(后文省略为Exception)和java.lang.Error(后文省略为Error),其中Error由JVM虚拟机进行管理,如我们所熟知的OutOfMemoryError异常。
Exception异常有个比较重要的子类,叫做RuntimeException。我们将RuntimeException或其他继承自RuntimeException的子类称为非受检异常(unchecked Exception),其他继承自Exception异常的子类称为受检异常(checked Exception)。

  • Call Service Exception! com.netflix.zuul.exception.ZuulException: Forwarding error
    超时时间不够。

# Java 异常处理的 10 个良心建议

back

  • 1、尽量不要使用e.printStackTrace(),而是使用log打印。
    • printStackTrace()打印出的堆栈日志跟业务代码日志是交错混合在一起的,排查异常日志不太方便。
    • e.printStackTrace()语句产生的字符串记录的是堆栈信息,如果信息太长太多,字符串常量池所在的内存块没有空间了,即内存满了,那么,用户的请求就卡住啦~
  • 2、catch了异常,但是没有打印出具体的exception,无法更好定位问题
  • 3、不要用一个Exception捕捉所有可能的异常
    • 用基类 Exception 捕捉的所有可能的异常,如果多个层次都这样捕捉,会丢失原始异常的有效信息哦
  • 4、记得使用finally关闭流资源或者直接使用try-with-resource
  • 5、捕获异常与抛出异常必须是完全匹配,或者捕获异常是抛异常的父类
// 反例
//BizException 是 Exception 的子类
public class BizException extends Exception {}
//抛出父类Exception
public static void test() throws Exception {}

try {
    test(); //编译错误
} catch (BizException e) { //捕获异常子类是没法匹配的哦
    log.error(e);
}



// 正例
//抛出子类Exception
public static void test() throws BizException {}

try {
    test();
} catch (Exception e) {
    log.error(e);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • 6、注意异常对你的代码层次结构的侵染(早发现早处理)
    • 我们的项目,一般都会把代码分 Action、Service、Dao 等不同的层次结构,如果你是DAO层处理的异常,尽早处理吧,如果往上 throw SQLException,上层代码就还是要try catch处理啦,这就污染了你的代码~
// 反例
public UserInfo queryUserInfoByUserId(Long userid) throw SQLException {
    //根据用户Id查询数据库
}



// 正例
public UserInfo queryUserInfoByUserId(Long userid) {
    try{
        //根据用户Id查询数据库
    }catch(SQLException e){
        log.error("查询数据库异常啦,{}",e);
    }finally{
        //关闭连接,清理资源
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 7、自定义封装异常,不要丢弃原始异常的信息Throwable cause

我们常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。公司的框架提供统一异常处理就用到异常链,我们自定义封装异常,不要丢弃原始异常的信息,否则排查问题就头疼啦

// 反例
//MyException 构造器
public MyException(String message) {
        super(message);
    }


// 正例
//MyException 构造器
public MyException(String message, Throwable cause) {
        super(message, cause);
    }
1
2
3
4
5
6
7
8
9
10
11
12
  • 8、运行时异常RuntimeException ,不应该通过catch 的方式来处理,而是先预检查,比如:NullPointerException处理
// 反例
try {
  obj.method();
} catch (NullPointerException e) {
...
}


// 正例
if (obj != null){
   ...
}
1
2
3
4
5
6
7
8
9
10
11
12
  • 9、注意异常匹配的顺序,优先捕获具体的异常
    • 注意异常的匹配顺序,因为只有第一个匹配到异常的catch块才会被执行。
// 反例
try {
    doSomething("test exception");
} catch (IllegalArgumentException e) {
    log.error(e);
} catch (NumberFormatException e) {
    log.error(e);
}

// 正例

try {
    doSomething("test exception");
} catch (NumberFormatException e) {
    log.error(e);
} catch (IllegalArgumentException e) {
    log.error(e);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

因为NumberFormatException是IllegalArgumentException 的子类,反例中,不管是哪个异常,都会匹配到IllegalArgumentException,就不会再往下执行啦,因此不知道是否是NumberFormatException。所以需要优先捕获具体的异常,把NumberFormatException放前面~

# 内存泄露

back

  • 静态集合类引起内存泄露
  • 监听器:
    • 但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
    • 各种连接,数据库、网络、IO等
  • 内部类和外部模块等的引用:
    • 内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。
    • 非静态内部类的对象会隐式强引用其外围对象,所以在内部类未释放时,外围对象也不会被释放,从而造成内存泄漏
  • 单例模式:
    • 不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象-的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露
  • 其它第三方类