文章插图
异常堆栈丢失今天登陆服务器进行例行的检查,发现异常日志文件里有很多nullPointException,只有简单的异常名称,却没有堆栈信息 。没有异常堆栈,无法定位错误,也就不能修改了 。到网上搜索信息,原来大家也遇到过这样的问题 。
正确的解决方法是增加一个VM Options:-XX:-OmitStackTraceInFastThrow 。这个参数的好处如下:
“JVM对一些特定的异常类型做了Fast Throw优化,如果检测到在代码里某个位置连续多次抛出同一类型异常的话,C2会决定用Fast Throw方式来抛出异常,而异常Trace即详细的异常栈信息会被清空 。这种异常抛出速度非常快,因为不需要在堆里分配内存,也不需要构造完整的异常栈信息 。”这个参数,支持的异常类型如下:
- NullPointerException
- ArithmeticException
- ArrayIndexOutOfBoundsException
- ArrayStoreException
- ClassCastException
解决问题后,进行了一番的测试和验证,如下:
问题验证众多网络文章,都指出在异常出现5000次以上时,才会丢失堆栈信息 。按照网络文章的测试代码,在6600多次的时候丢失堆栈信息,但是并不稳定 。代码如下:
public class JAVANPE extends Thread {private static int count = 0;@Overridepublic void run() {try {System.out.println("getSimpleName is:"+this.getClass().getSimpleName() + " execute count:" + (++count));String str = null;System.out.println(str.length());} catch (Throwable e) {e.printStackTrace();}}}public class TestFastThrow {public static void main(String[] args) throws InterruptedException {JavaNPE javaNPE = new JavaNPE();ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < Integer.MAX_VALUE; i++) {executorService.execute(javaNPE);//防止打出的日志太快Thread.sleep(2);}}}
这是一个多线程的程序,写单线程的测试程序,是否可行呢?于是就尝试了第一版,代码如下:public static void main(String args[]) {for (int i = 0; i < 10000; i++) {try {String value = https://www.isolves.com/it/cxkf/yy/JAVA/2020-04-18/null;value.substring(0, 100);} catch (Exception ex) {ex.printStackTrace();}}}
但是,很遗憾,这个程序的异常堆栈信息并不会丢失 。程序修改如下:public void method2() {String value = https://www.isolves.com/it/cxkf/yy/JAVA/2020-04-18/null;value.substring(0, 100);}public static void main(String args[]) {ExceptionTest exceptionTest = new ExceptionTest(1);for (int i = 0; i < 10000; i++) {try {exceptionTest.method2();} catch (Exception ex) {ex.printStackTrace();}}}
这个程序在第5530~5550次的时候,会丢失异常堆栈信息,是不稳定的 。分析上述两段单线程的测试代码,第一段因为异常信息没有到方法的外面,jvm不能追踪到异常堆栈信息,所以并不会起作用 。性能优化明显吗?首先,在网络上的文章,都着重说C2编译器的优化,并没有说明C1编译器 。通过我的实验,C1和C2编译器的默认行为是一样的,都是开启OmitStackTraceInFastThrow的 。C1和C2编译器,是指Java及时编译技术(JIT),在启动应用程序是,java -client是启动C1编译器;java -server是启动C2编译器 。
这个参数真的有性能优化吗?测试代码没有IO输出,纯测试代码的执行速度,测试代码如下:
public void testPerformance() {long start = System.currentTimeMillis();System.out.println("start");for (int i = 0; i < 1000 * 1000; i++) {try {this.method();} catch (Exception ex) {}}System.out.println("used " + (System.currentTimeMillis() - start));}public void method() {String value = https://www.isolves.com/it/cxkf/yy/JAVA/2020-04-18/null;value.substring(0, 100);}
测试结果如下:文章插图
测试结果发现,关闭堆栈后性能确实有了很大的提升,在输出堆栈的情况下,性能随着调用次数的增多,呈线性增长 。
测试代码中的堆栈信息,只有一层,把调用异常堆栈的深度增加到20,是不是创建的对象而更多,耗时时间更长呢?关闭参数的情况下,确实是,调用1M次,时间增加到由13秒增加到20秒;启用参数的情况下,耗时却从400毫秒减少到150毫秒!
经测试发现:同样都是在5500多次的时候,丢掉异常的堆栈信息 。并且20层异常堆栈,执行这5500多次,耗时120毫秒;层数较少的只用了70毫秒 。也就是说,堆栈层数越多,丢掉堆栈后的性能越好!具体是什么原因,就需要阅读源代码了!如果你知道,欢迎留言探讨 。
推荐阅读
- 从 SQL 到 MongoDB,这一篇就够了
- 宽带|一图科普:光纤是怎么从运营商机房到你家的?
- 佩戴银饰有什么讲究?
- 刘嘉玲|刘嘉玲从118斤瘦到90斤,做普拉提瘦身效果超赞,孙俪也经常做
- 就业|被996吓退?88%的00后愿意灵活就业:UP主成最想从事职业
- 治疗脚气的中药有哪些
- MySQL大表优化方案——从单表优化到分表分库
- 雅迪、爱玛、台铃,电动车哪个牌子好?从品牌、技术、配置讲明白
- 朋友从毛里求斯带来的红茶不知怎么喝,用煮吗?[红茶]
- 红茶;绿茶;单从茶的好处各是什么?[红茶]