踩坑记

| 分类 Java  | 标签 Java 

##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,然后就悲剧鸟= =归根到底还是怪自己没有认真看文档。得出的结论就是:

当使用新的类、新的方法时,一定要抽出几分钟浏览了一下文档,看看有没有什么坑,而且,千万不要根据函数名字猜功能,这个是兵家大忌。

看了文档之后,就知道怎么解决这个异常了:

  1. 每个线程新建一个instance
  2. 单例模式,但使用format()的时候加锁

这个大致估计一下就是方法2的效率高,于是找了个简单的例子测试一下,果然是2的耗时较少,于是使用方法2改进,之后观察了几天,发现这个异常没有再出现,算是解决了。

##3. 说多了都是泪

从上面几个找bug的过程总结出来的经验就是:

  • 使用新类、新函数时,一定一定要过一遍文档
  • 除了问题,第一选择还是stackoverflow吧,csdn真心不靠谱
  • 三天不看书,智商输给猪

##4. 后记

我勒个去,在淘宝中间件团队博客中有一篇文章和我一样,原来淘宝大神们也遇到过这个问题,看了人家的分析过程,真是太羞愧了。人家从发现、定位、解决、性能测试、观察都做了足够的功夫,而我仅仅是解决了异常为止,看来以后要对自己的代码负责。

给个链接,大家感受一下:淘宝大神如何解决问题


上一篇     下一篇