简介
Java异常是Java提供的一种识别及响应错误的一致性机制。
Throwable
Throwable 是 Java 中所有错误与异常的超类。
Throwable 包含两个子类:Error(错误)和 Exception(异常)
,通常用于指明异常情况。
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()
等接口用于获取堆栈跟踪数据等信息。
Error(错误)
Error是程序中无法处理的错误。
此类错误一般表示代码运行时JVM出现问题。通常有Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)。比如 OutOfMemoryError:内存溢出错误;StackOverflflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。这些错误是不受检异常(非受检异常),非代码性错误。
OOM(OutOfMemoryError)
为什么会没有内存了呢?原因不外乎有两点:
1)分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
2)应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
引起OOM主要有2个原因:
内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。(堆溢出)
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。(栈溢出)
最常见的OOM情况有以下三种:
java.lang.OutOfMemoryError: Java heap Space;
:java堆内存溢出,这种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
java.lang.OutOfMemoryError: PermGen Space;
:java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
java.lang.StackOverflowError
:不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
SOF(StackOverFlowError)- 堆栈溢出
如果线程请求的栈深度大于虚拟机所允许的深度,抛出该异常;
-
栈溢出的原因:
递归调用
大量循环或死循环
全局变量是否过多
数组、List、map数据过大
Exception(异常)
程序本身可以捕获并且可以处理的异常。
Exception 这种异常又分为两类:运行时异常和编译时异常。
异常的分类
受检异常与非受检异常
受检(checked)异常:编译器要求必须处理的异常。除 RuntimeException 及其子类外,其他的Exception 异常都属于受检异常。
非受检(unchecked)异常 :编译器不会进行检查并且不要求必须处理的异常,该类异常包括 运行时异常(RuntimeException及其子类) 和 错误(Error)
。
运行时异常(属于非受检异常)
定义:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。
Java 编译器不会检查它
。也就是说,当程序中可能出现这类异常时,既没有通过"throws"声明抛出它,也没有用"try-catch"语句捕获它,还是会编译通过。
RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获,该异常的出现绝大数情况是代码本身有问题,应该从逻辑上去解决
。
编译时异常(非运行时异常,属于受检异常)
定义: Exception 中除 RuntimeException 及其子类之外的异常。
Java 编译器会检查它
。编译器会检查此类异常,如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,要么使用try-catch捕获,要么使用throws语句抛出,否则编译不通过。
常用异常举例
异常 | 说明 |
---|---|
NullPointerException | 空指针异常 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 |
ArithmeticException | 算术异常,如除数为零 |
ClassNotFoundException | 不能加载所需要的类 |
IllegalArgumentException | 接受非法参数 |
InvocationTargetException | 当被调用的方法内部抛出异常未被捕获时,会抛出这个异常 |
SQLException | 操作数据库异常 |
IOException | 输入输出异常 |
InputMisMatchException | 类型不匹配 |
NumberFormatException | 格式化数据异常 |
FileNotFoundException | 文件未找到异常 |
InstantiationException | 实例化异常 当试图通过Class的newInstance方法创建某个类的实例,但程序无法通过该构造器来创建该对象时引发 |
IllegalAccessException | 没有访问权限 |
ClassCastException | 类型转换异常 |
处理异常
分为两块:抛出异常 和 捕获异常。
throw,throws
throw
语法:throw (异常对象);
throw:将产生的异常抛出,是抛出异常的一个动作。
一般会用于程序出现某种逻辑时,程序员主动抛出某种特定类型的异常,throw是明确了这个地方要抛出这个异常。
throws
throws:声明将要抛出何种类型的异常。
当某个方法可能会抛出某种异常时,用throws 声明可能抛出的异常,然后交给上层调用它的方法程序处理。
throws E1,E2,E3只是告诉程序这个方法可能会抛出这些异常,方法的调用者可能要处理这些异常,而这些异常E1,E2,E3可能是该函数体产生的。
public void show() throws RemoteException{
// 抛出异常
throw new RemoteException();
}
throw与throws的区别
不同点 | throw | throws |
---|---|---|
位置 | 出现在函数体(方法体内) | 出现在方法函数头 |
是否出现 | 执行throw则一定抛出了某种异常对象 | 表示出现异常的一种可能性,并不一定会发生这些异常 |
处理 | 由方法体内的语句处理 | 由该方法的调用者来处理 |
相同点:两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用来处理。
try,catch,finally
介绍
try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
// 多重捕获块
try {
// 代码块
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 其它处理;
}
try语句块不可以独立存在,必须与 catch 或者 finally 块同存。也就是存在3种情况:try-catch,try-finally,try-catch-finally。
多个catch块处理的异常类,要按照先catch子类 后catch父类
的处理方式(顺序不对编译器会提醒错误)。catch 不能独立于 try 存在。
无论是否发生异常,finally 代码块中的代码总会被执行,前提是try执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句,用于关闭和释放资源。
在 try/catch 后面添加 finally 块并非强制性要求的,try,catch,finally 块之间不能添加任何代码。
至少有两种情况下finally语句是不会被执行的
- try语句没有被执行到,如在try语句之前就返回了,这样finally语句就不会执行,这也说明了finally语句被执行的必要而非充分条件是:
相应的try语句一定被执行到
。 - 在try块中有System.exit(0);这样的语句,System.exit(0);是终止Java虚拟机JVM的,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到。
try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会执行,finally是在try或catch中的retrun语句执行后,return返回之前执行的。
try或catch中的return语句已经执行了,不过并没有直接返回,而是等finally语句执行完了再返回结果。
如果finally里有return,则直接返回,就不管try或catch中是否还有返回语句了。finally里加上return后,finally外面的代码就变成不可达语句了,也就是永远不能被执行到,所以需要注释掉否则编译器报错。
try或catch中有return X,finally修改 X,返回值是否有影响?
如果finally中有return,则直接返回,就不管try或catch中是否还有返回语句。
如果finally中没有return,考虑返回值类型。如果返回值是以地址返回的,finally中修改则影响最后的结果(数组,List,Map,Set…);如果返回值是以实际值返回的,finally中修改不影响结果(int,Integer,String…)。
finally中修改返回值的做法是不好的,应该避免。finally中存在return也是不好的做法,应该避免。
public class Test {
public static void main(String[] args) {
// int
System.out.println("返回结果:" + new Test().test());
System.out.println();
// String
System.out.println("返回结果:" + new Test().test1());
System.out.println();
// String[]
String[] a = new Test().test2();
System.out.println("返回结果:" + a[0]);
System.out.println();
// Integer
System.out.println("返回结果:" + new Test().test3());
}
public int test() {
int a = 100;
try {
System.out.println("try异常前");
System.err.println(a / 0);
System.out.println("try异常后");
a = 10;
} catch (ArithmeticException e) {
System.out.println("catch子异常");
a = 20;
return a;
} catch (Exception e) {
System.out.println("catch父异常");
a = 30;
return a;
} finally {
System.out.println("finally");
a = 40;
}
System.out.println("外部");
return a;
}
public String test1() {
String a = "100";
try {
System.out.println("try异常前1");
System.err.println(100 / 0);
System.out.println("try异常后");
a = "10";
} catch (ArithmeticException e) {
System.out.println("catch子异常");
a = "20";
return a;
} catch (Exception e) {
System.out.println("catch父异常");
a = "30";
return a;
} finally {
System.out.println("finally1");
a = "40";
}
System.out.println("外部");
return a;
}
public String[] test2() {
String[] a = { "0" };
try {
System.out.println("try异常前2");
System.err.println(100 / 0);
System.out.println("try异常后");
a[0] = "10";
} catch (ArithmeticException e) {
System.out.println("catch子异常");
a[0] = "20";
return a;
} catch (Exception e) {
System.out.println("catch父异常");
a[0] = "30";
return a;
} finally {
System.out.println("finally2");
a[0] = "40";
}
System.out.println("外部");
return a;
}
public Integer test3() {
Integer a = Integer.valueOf("0");
try {
System.out.println("try异常前3");
System.err.println(100 / 0);
System.out.println("try异常后");
a = Integer.valueOf("10");
} catch (ArithmeticException e) {
System.out.println("catch子异常");
a = Integer.valueOf("20");
return a;
} catch (Exception e) {
System.out.println("catch父异常");
a = Integer.valueOf("30");
return a;
} finally {
System.out.println("finally3");
a = Integer.valueOf("40");
}
System.out.println("外部");
return a;
}
}
运行结果:
try异常前
catch子异常
finally
返回结果:20
try异常前1
catch子异常
finally1
返回结果:20
try异常前2
catch子异常
finally2
返回结果:40
try异常前3
catch子异常
finally3
返回结果:20
自定义异常
Java中要想创建自定义异常,需要继承Throwable或者他的子类Exception。
- 所有异常都必须是 Throwable 的子类
- 如果写一个检查性异常类,需要继承 Exception 类
- 如果写一个运行时异常类,需要继承 RuntimeException 类
public class MyException extends Exception {
// 一个无参构造函数
public MyException(){ }
// 一个带有详细描述信息的构造函数
// Throwable的 toString 方法会打印这些详细信息,调试时很有用
public MyException(String msg){
super(msg);
}
// ...
}