##1. 空指针异常
前阵子观察线上服务时,发现tomcat日志中有很多空指针异常,但是很诡异的是:没有对应的堆栈。只有一句提示:java.lang.NullPointerException
,而且量还比较大。因为没有堆栈,所以没法定位是哪里抛出的异常,也就更没法解决。
因为正好看过了一点 JVM 的东西,知道 JVM 有一个线程堆栈大小的参数,Xss。于是第一反应是这个参数过小,而这个空指针异常因为堆栈信息过多,导致溢出,所以就只打印了一个异常名字。于是看了我们项目配置的线程堆栈大小:java -XX:+PrintFlagsFinal | grep 'ThreadStackSize'
,结果发现使用的是 JVM 的默认值:1024K,也就是1M。按照这个大小,堆栈一般不会超出这个范围,于是确实不是这个原因。
于是上网求助,在CSDN上发了一个帖子,然后几个人回复的结果是:人肉代码。。。果断跪了,于是去stackoverflow发帖,然后来一个repu 60K的高手,说这个问题他也遇到去,去看下 JVM 的文档就清楚了,于是我去搜了一下文档,果然找到了:链接,具体原因在这里写的:
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.
大概翻译一下,就是为了性能,当一个异常频繁抛出,就会被 JIT 重新编译,编译后会去掉堆栈信息,只打印异常。如果要取消这个优化,就要关闭这个选项:-XX:-OmitStackTraceInFastThrow
于是找了一台机器禁止了这个优化后,终于打印了详细的堆栈信息,愉快的解决了空指针异常。
##2. 数组越界异常
因为当时只启用了一台服务器关闭这个优化,过了一阵子又出现了一个数组越界异常,使用同样方法发现异常是这样产生的:
1
2
3
4
5
6
7
8
9
10
Caused by: java.lang.ArrayIndexOutOfBoundsException: -1831238
at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:436)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2081)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:1996)
at java.util.Calendar.setTimeInMillis(Calendar.java:1109)
at java.util.Calendar.setTime(Calendar.java:1075)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:876)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:869)
at java.text.DateFormat.format(DateFormat.java:316)
对,就是DateFormat抛出的异常,于是很奇怪的去看了文档:DataFormat,发现在doc的最后一点有个同步说明:
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
额,它竟然把最容易出错的东西放到最后,这个。。。因为创建一个DateFormat对象代价较高,就多个线程共用了一个instance,然后就悲剧鸟= =归根到底还是怪自己没有认真看文档。得出的结论就是:
当使用新的类、新的方法时,一定要抽出几分钟浏览了一下文档,看看有没有什么坑,而且,千万不要根据函数名字猜功能,这个是兵家大忌。
看了文档之后,就知道怎么解决这个异常了:
- 每个线程新建一个instance
- 单例模式,但使用format()的时候加锁
这个大致估计一下就是方法2的效率高,于是找了个简单的例子测试一下,果然是2的耗时较少,于是使用方法2改进,之后观察了几天,发现这个异常没有再出现,算是解决了。
##3. 说多了都是泪
从上面几个找bug的过程总结出来的经验就是:
- 使用新类、新函数时,一定一定要过一遍文档
- 除了问题,第一选择还是stackoverflow吧,csdn真心不靠谱
- 三天不看书,智商输给猪
##4. 后记
我勒个去,在淘宝中间件团队博客中有一篇文章和我一样,原来淘宝大神们也遇到过这个问题,看了人家的分析过程,真是太羞愧了。人家从发现、定位、解决、性能测试、观察都做了足够的功夫,而我仅仅是解决了异常为止,看来以后要对自己的代码负责。
给个链接,大家感受一下:淘宝大神如何解决问题