你好呀,我是why。
你猜这次我又要写个啥没有卵用的知识点呢?
不好意思,问的稍微有点早了,啥提示都没给,咋猜呢,对吧?
先给你上个代码:
public class ExceptionTest { public static void main(String[] args) { String msg = null; for (int i = 0; i < 500000; i++) { try { msg.toString(); } catch (Exception e) { e.printStackTrace(); } } } }
来,就这代码,你猜猜写出个什么花儿来?
当然了,有猜到的朋友,也有没猜到的朋友。
很好,那么请猜出来了的同学迅速拉到文末,完成一键三连的任务后,就可以出去了。
没有猜出来的同学,我把代码一跑起来,你就知道我要说啥了:
一瞬间的事儿,瞅见了吗?神奇吗?产生疑问了吗?
在抛出一定次数的空指针异常后,异常堆栈没了。
这就是我标题说的:太扯了吧?异常信息突然就没了。
你说为啥?
为啥?
这事就得从 2004 年讲起了。
那一年,SUN 公司于 9 月 30 日 18 点发布了 JDK 5。
在其 release-notes 中有这样一段话:
https://www.oracle.com/java/technologies/javase/release-notes-introduction.html
主要是框起来的这句话,看不明白没关系,我用我八级半的英语给你翻译一下。
我们一句句的来:
The compiler in the server VM now provides correct stack backtraces for all "cold" built-in exceptions.
对于所有的内置异常,编译器都可以提供正确的异常堆栈的回溯。
For performance purposes, when such an exception is thrown a few times, the method may be recompiled.
出于性能的考虑,当一个异常被抛出若干次后,该方法可能会被重新编译。(重要)
After recompilation, the compiler may choose a faster tactic using preallocated exceptions that do not provide a stack trace.
在重新编译之后,编译器可能会选择一种更快的策略,即不提供异常堆栈跟踪的预分配异常。(重要)
To disable completely the use of preallocated exceptions, use this new flag: -XX:-OmitStackTraceInFastThrow.
如果要禁止使用预分配的异常,请使用这个新参数:-XX:-OmitStackTraceInFastThrow。
这几句话先不管理解没有。但是至少知道它这里描述的场景不就是刚刚代码演示的场景吗?
它最后提到了一个参数 -XX:-OmitStackTraceInFastThrow
,二话不说,先拿来用了,看看效果再说:
同样的代码,加入该启动参数后,异常堆栈确实会从头到尾一直打印。
不知道你感觉到没有,加入该启动参数后,程序运行时间明显慢了很多。
在我的机器上没加该参数,程序运行时间是 2826 ms,加上该参数运行时间是 5885 ms。
说明确实是有提升性能的功能。
到底是咋提升的,下一节说。
先说个其他的。
这里都提到 JVM 参数了,我顺便再分享一个网站:
该网站提供了很多功能,这是其中的几个功能:
JVM 参数查询功能那必须得有:
很好用的,你以后遇到不知道是干啥用的 JVM 参数,可以在这个网站上查询一下。
到底为啥?
前面讲了是出于性能原因,从 JDK 5 开始会出现异常堆栈丢失的现象。
那么性能问题到底在哪?
来,我们一起看一下最常见的空指针异常。
以本文为例,看一下异常抛出的时候调用路径:
最终会走到这个 native 方法:
java.lang.Throwable#fillInStackTrace(int)
fill In Stack Trace,顾名思义,填入堆栈跟踪。
这个方法会去爬堆栈,而这个过程就是一个相对比较消耗性能的过程。
为啥比较耗时呢?
给你看个比较直观的:
这类的异常堆栈才是我们比较常见的,这么长的堆栈信息,可不消耗性能吗。
现在,我们现在再回去看这句话:
For performance purposes, when such an exception is thrown a few times, the method may be recompiled. After recompilation, the compiler may choose a faster tactic using preallocated exceptions that do not provide a stack trace.
出于性能的考虑,当一个异常被抛出若干次后,该方法可能会被重新编译。在重新编译之后,编译器可能会选择一种更快的策略,即不提供异常堆栈跟踪的预分配异常。
所以,你能明白,这个“出于性能的考虑”这句话,具体指的就是节约 fillInStackTrace(爬堆栈)的这个性能消耗。
更加深入一点的研究对比,你可以看看这个链接:
http://java-performance.info/throwing-an-exception-in-java-is-very-slow