一.如何自定义异常

  1. public class MyException extends RuntimeException {
  2. public MyException() {
  3. super();
  4. }
  5. public MyException(String message) {
  6. super(message);
  7. }
  8. public MyException(Throwable cause) {
  9. super(cause);
  10. }
  11. public MyException(String message, Throwable cause) {
  12. super(message, cause);
  13. }
  14. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. method1();
  4. }
  5. private static void method1() {
  6. method2();
  7. }
  8. private static void method2() {
  9. method3();
  10. }
  11. private static void method3() {
  12. throw new MyException("自定义异常");
  13. }
  14. }

截屏2021-12-04 下午3.48.04.png

二.为什么要优化

抛异常耗时5061ms:

  1. public class Test {
  2. public static void main(String[] args) {
  3. long start = System.currentTimeMillis();
  4. for (int i = 0; i < 10000000; i++) {
  5. try {
  6. method1();
  7. } catch (MyException e) {
  8. }
  9. }
  10. System.out.println(System.currentTimeMillis() - start);
  11. }
  12. private static void method1() {
  13. method2();
  14. }
  15. private static void method2() {
  16. method3();
  17. }
  18. private static void method3() {
  19. throw new MyException("自定义异常");
  20. }
  21. }

不抛异常耗时4ms:

  1. private static void method3() {
  2. new Object();
  3. }

三.抛异常耗时的原因

MyException的构造方法调用了父类的构造方法,最终会调到Throwable的构造方法。

  1. public Throwable(String message) {
  2. fillInStackTrace();
  3. detailMessage = message;
  4. }
  1. public synchronized Throwable fillInStackTrace() {
  2. if (stackTrace != null ||
  3. backtrace != null) {
  4. // 爬异常堆栈信息,耗时
  5. fillInStackTrace(0);
  6. stackTrace = UNASSIGNED_STACK;
  7. }
  8. return this;
  9. }
  10. private native Throwable fillInStackTrace(int dummy);

四.优化

当我们的自定义异常不需要异常堆栈信息时,可以重写fillInStackTrace方法来提升性能。

  1. @Override
  2. public Throwable fillInStackTrace() {
  3. return this;
  4. }

优化后耗时90ms。

五.JVM对内置异常的优化

  1. public class Test {
  2. public static void main(String[] args) {
  3. String s = null;
  4. long start = System.currentTimeMillis();
  5. for (int i = 0; i < 116000; i++) {
  6. try {
  7. s.length();
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. System.out.println(System.currentTimeMillis() - start);
  13. }
  14. }

截屏2021-12-04 下午4.14.20.png
在抛出一定次数的空指针异常后,异常堆栈没了。
出于性能的考虑,当一个异常被抛出若干次后,该方法可能会被重新编译。在重新编译之后,编译器可能会选择一种更快的策略,即抛出一个事先分配好的异常对象,这个对象的堆栈信息为空。
可以调用异常对象的hashcode方法,发现后面的值是一样的。
截屏2021-12-04 下午4.21.44.png
循环100万次耗时3968ms。如果想禁用优化,打印出堆栈信息,可以设置一个jvm参数:-XX:-OmitStackTraceInFastThrow,设置后耗时12270ms。