上周在线上系统发现了两个bug,值得记录下查找的过程和原因。以后如果还有查找bug比较有价值的经历,我也会继续分享。
第一个bug的起始,是在线上日志发现一个频繁打印的异常——java.lang.ArrayIndexOutOfBoundsException。但是却没有堆栈,只有一行一行的ArrayIndexOutOfBoundsException。没有堆栈,不知道异常是从什么地方抛出来的,也就不能找到问题的根源,更谈不上解决。题外,工程师在用log4j记录错误异常的时候,我看到很多人这样用(假设e是异常对象):
log.error("发生错误:"+e);
或者:
log.error("发生错误:"+e.getMessage());
这样的写法是不对,只记录了异常的信息,而没有将堆栈输出到日志,正确的写法是利用error的重载方法:
log.error("xxx发生错误",e);
这样才能在日志中完整地输出异常堆栈来。如何写好日志是另一个话题,这里不展开。继续我们的找bug经历。刚才提到,我们线上日志一直出现一行错误信息ArrayIndexOutOfBoundsException却没有堆栈,是我们没有正确地写日志吗?检查代码不是的,这个问题其实是跟JDK5引入的一个新特性有关,对于一些频繁抛出的异常,JDK为了性能会做一个优化,在JIT重新编译后会抛出没有堆栈的异常。在使用server模式的时候,这个优化是开启的,我们的服务器跑在server模式下并且jdk版本是6,因此在频繁抛出ArrayIndexOutOfBoundsException异常一段时间后优化开始起作用,只抛出没有堆栈的异常信息了。
那么怎么解决这个问题呢?因为这个优化是在JIT重新编译后才起作用,因此一开始抛出异常的时候还是有堆栈的,所以可以查看较旧的日志,寻找有完整堆栈的异常信息。但是由于我们的日志太大,会定期删除,我们的服务器也启动了很长时间,因此查找日志不是很靠谱的方法。
另一个解决办法是暂时禁用这个优化,强制要求每次都要抛出有堆栈的异常。幸好JDK提供了选项来关闭这个优化,配置JVM参数
-XX:-OmitStackTraceInFastThrow
就可以禁止这个优化(注意选项中的减号,加号是启用)。
我们找了台机器,配置了这个参数并重启一下。过了一会就找到问题所在,堆栈类似这样
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)
读者肯定猜到了,这个问题是由于SimpleDateFormat的误用引起的。SimpleDateFormat的javadoc中有这么句话:
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.
但是很悲剧的是这句话是放在整个doc的最后面,在我看来,这句话应该放在最前面才对。简单来说就是SimpleDateFormat不是线程安全的,你要么每次都new一个来用,要么做加锁来同步使用。
出问题的代码就是由于工程师认为SimpleDateFormat的创建代价很高,然后搞了个map做缓存,所有线程共用这个instance做format,同时没有做同步。悲剧就诞生了。
这里就引出我想提到的第二点问题,在使用一个类或者方法的时候,最好能详细地看下该类的javadoc,JDK的javadoc是做的非常好的,javadoc除了做说明之外,通常还会给示例,并且会点出一些关键问题,如线程安全性和平台移植性。
最后,我将做个测试,到底在使用SimpleDateFormat怎么做才是最好的方式?假设我们要实现一个formatDate方法将日期格式化成"yyyy-MM-dd"的格式。
第一个方法是每次使用都创建一个instance,并调用format方法:
public static String formatDate1(Date date) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
return format.format(date);
}
第二个方法是只创建一个instance,但是在调用方法的时候做同步:
private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
public static synchronized String formatDate2(Date date) {
return formatter.format(date);
}
第三个方法比较特殊,我们为每个线程都缓存一个instance,存放在ThreadLocal里,使用的时候从ThreadLocal里取就可以了:
private static ThreadLocal<SimpleDateFormat> formatCache = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
public static String formatDate3(Date date) {
SimpleDateFormat format = formatCache.get();
return format.format(date);
}
然后我们测试下三个方法并发调用下的性能并做一个比较,并发100个线程循环调用1000万次,记录耗时。我们设置了JVM参数:
-Xmx512m -XX:CompileThreshold=10000
设置堆最大为512M,设置当一个方法被调用1万次的时候就被JIT编译。测试的结果如下:
|
第1次测试
|
第2次测试
|
第3次测试
|
formatDate1
|
50545
|
49365
|
53532
|
formatDate2
|
10895
|
10761
|
10673
|
formatDate3 |
10386 |
9919 |
9527 |
(单位:毫秒)
从结果来看,方法1最慢,方法3最快,但是就算是最慢的方法1也可以达到每秒钟200 20万次的调用量,很少有系统能达到这个量级。这个点很难成为你系统的瓶颈所在。从我的角度出发,我会建议你用方法1或者方法2,如果你追求那么一点性能提升的话,可以考虑用方法3,也就是用ThreadLocal做缓存。
总结下本文找bug经历想表达的几点想法:
(1)正确地打印错误日志
(2)在server模式下,最好都设置-XX:-OmitStackTraceInFastThrow
(3)使用类或者方法的时候,最好能详细阅读下javadoc,很多问题都能找到答案
(4)使用SimpleDateFormat的时候要注意线程安全性,要么每次new,要么做同步,两者的性能有差距,但是这个差距很难成为你的性能瓶颈。
转自:http://www.blogjava.net/killme2008/archive/2011/07/10/354062.html
分享到:
相关推荐
这个过程在软件开发团队中非常实用,因为它允许团队成员分析Bug趋势,找出最常见的问题,或者评估修复工作的进度。同时,Excel的灵活性使得可以轻松地进行排序、过滤和数据可视化,以帮助决策。 在提供的压缩包文件...
开发人员会复现问题,分析代码找出原因,并开始编写修复代码。此时,Bug的状态可能更新为“处理中”。 5. **修复与验证**:开发人员完成修复后,会提交代码并创建一个修复版本。然后,测试人员需要重新执行之前的...
同时,通过定期分析BugFree提供的报告,团队可以找出问题的瓶颈,改进开发和测试策略,从而提升软件的整体质量。 总之,BugFree作为一款免费且功能完善的缺陷管理工具,对于中小型企业或开源项目来说,是一个非常...
本文将深入探讨如何在VS2010基于MFC的项目中有效地查找和修复bug,以及如何实现优雅的程序崩溃。 首先,了解MFC框架是必要的。MFC是微软为Windows平台提供的一个C++库,它封装了Windows API,使得开发者可以使用...
此外,学会使用日志工具记录程序运行状态,也有助于找出问题的根源。 总之,这份"java的BUG集锦以及初学者上级练习素材全套"资源为初学者提供了一个全面的学习和实践平台,通过分析错误报告,完成实战练习,可以...
1. **复现问题**:首先需要在开发环境中复现bug,通过日志记录和调试工具找出问题发生的上下文。 2. **定位问题**:根据错误信息或异常行为,定位到代码的具体位置,查看相关的数据流和控制流。 3. **修复代码**:...
4. **工具应用**:熟悉并掌握各种开发工具,如IDE的调试功能、代码静态检查工具(如SonarQube、PMD),以及版本控制工具(如Git),它们可以帮助我们更有效地找出和修复bug。 5. **错误预防**:理解bug产生的原因,...
7. **调试与日志**:利用vc2005_bug.txt中的信息,结合调试工具进行逐步调试,找出问题所在。如果日志不够详尽,考虑增加更多的日志输出以帮助诊断。 8. **社区支持**:访问微软开发者论坛或其他技术社区,寻找类似...
1. **问题定位**:通过观察和记录异常现象,使用示波器、逻辑分析仪等工具获取故障时的信号波形,配合软件日志,找出可能出错的地方。 2. **软件调试**:检查与GT9147交互的驱动程序代码,确保通信协议正确无误,如...
4. 调试时,使用Log或调试工具查看数据与视图的对应关系,以找出问题所在。 通过以上分析,我们可以看到,Android开发中的这类bug往往源于视图复用机制的错误配置。理解并正确使用convertView是避免这类问题的关键...
6. **模型解释**:理解模型的预测决策过程,找出导致错误的代码模式,为改进代码提供指导。 7. **应用与迭代**:将模型应用于实际项目中,根据反馈调整和更新模型,持续优化错误预测效果。 "Bug-Prediction-...
6. **调试与修复**:解决问题通常涉及调试代码,找出导致下拉即刷新的错误触发点,可能是事件绑定错误、滚动判断条件不当或者是第三方库的配置问题。修复可能涉及修改事件处理函数、调整判断逻辑,或者更新和配置...
新增右键菜单:转二维码 新增手动检测到新版本资源库时的提示 版本 v19.01.11 新增资源库可选择联网更新功能 新增清空列表前提示确认 新增加载收藏夹前提示确认 新增可选是否启用广告过滤 新增清晰度选择保存记录 ...
总之,软件测试不仅是找出错误,也是确保软件质量的过程。从需求分析到测试执行,再到BUG管理和测试总结,每一个环节都需要细致入微的关注和专业技能。掌握这些基础知识,能帮助测试人员在行业中稳步发展,为软件...
易语言5.1 相对于易语言5.0更新说明: ... 修改XML解析支持库,增加写出CDATA数据功能,解决解析XML时错误的丢弃换行和TAB字符的BUG,解决读取节点值时对CDATA数据进行转义处理的BUG。 20. 修改扩展界面支持库...
测试管理与QualityCenter培训手册 1、测试流程管理、测试度量方法 按照尽早进行测试的原则,测试人员应该在需求阶段就介入,并贯穿软件开发的全过程。就测试过程本身而言,应该包含以s下几个阶段。...
在过程中,需要找出关键代码,分析日志,使用可运行的软件体验业务逻辑,以此来加深对源码的理解。 文章还提出了一些个人经验和建议,比如调试时的注意事项,阅读源码时应保持的好奇心和细致程度,以及在阅读过程中...
在三星手机上,时间会走得比较快,由于没有真机,实在找不到问题所在 [2008.1.1] Ver:3.2.1 修正了阅读历史图标显示不正确的BUG 修正了阅读HTML文件时,不显示空格的BUG 修正HTML阅读时解析不正确的BUG 提升HTML...
开发团队需要对每个问题进行详尽的分析,找出根本原因,并提出有效的解决方案,以确保系统的正常运行和用户满意度。 总的来说,【我的CRM系统】是一个集成了系统设计、SQL数据库技术、网页设计艺术和深度数据分析的...
3. 集中区域:找出MIDI文件中音符值最常出现的区间。 4. 跨度:音符值的差异,反映音乐的音域宽广度。 5. 时间间隔:音符的持续时间和间隔,影响音乐的动态和节奏。 解决上述问题的一种方法可能是采用更灵活的数据...