案例分享-Exception.getMessage突然为null
背景
之前做的小工具一个jsqlparse+git做的小工具帮我节省时间摸鱼昨天突然停止工作,看了下jvm并没有退出,但是看日志确实有不少Error输出,虽说是一个普通的NPE,但是分析了一下却疑点重重,所以花点时间来一探究竟,最终又掌握一个jvm知识点,还是比较有意思。
错误现场
以下是示例代码,为了说明问题做了简化,大概意思是使用CCJSqlParserUtil去解析一段sql语句,如果解析出错了以后从JSQLParserException.getMessage()中利用正则提取出具体的行和列。
Statements statements = null; Set<Integer> sqlSet = new HashSet<>(); String sql ="alter table test add column varchra(4)"; try { statements = CCJSqlParserUtil.parseStatements(sql); } catch (JSQLParserException e) { Pattern pattern = Pattern.compile("line (\\d+), column (\\d+)"); String message = e.getMessage(); Matcher m = pattern.matcher(message); int line = -1; int column = -1; while(m.find()){ int groupCount = m.groupCount(); if(groupCount > 0){ line = Integer.parseInt(m.group(1)); column = Integer.parseInt(m.group(2)); break; } } }
上面那个错误sql解析出错了以后的异常信息如下:
Encountered unexpected token: "varchra" <S_IDENTIFIER> at line 1, column 29. Was expecting: "COMMENT"
那个诡异的NPE 栈如下:
java.lang.NullPointerException: null at java.util.regex.Matcher.getTextLength(Matcher.java:1283) at java.util.regex.Matcher.reset(Matcher.java:309) at java.util.regex.Matcher.<init>(Matcher.java:229) at java.util.regex.Pattern.matcher(Pattern.java:1093) at xxx.ScriptUtil.sqlParse(ScriptUtil.java:41)
很显然是e.getMessage()返回了null导致pattern.matcher(message)失败,但是e.getMessage()理论上来讲不会是null,有点玄学的味道,一般解决玄学的首要方法是重启大法(个人观点,欢迎来喷,哈哈)。果然,重启了以后竟然好了,好奇心一下就被激发了。
错误原因
网上一通搜索确实类似的案例不少,大概的意思是jvm对异常处理这块做了优化,如果频繁抛出某种异常jvm会对这些异常做一些处理,使用JVM初始化的时候创建的那些异常对象来替代本应该新建的异常对象,因此这些异常栈和Message是空的,这一特性受OmitStackTraceInFastThrow参数的管控,可以通过-XX:+OmitStackTraceInFastThrow开启,或者-XX:-OmitStackTraceInFastThrow关闭,看完确实恍然大悟,但是并没有找到官方的一些说明,还是心有不甘,决定在openjdk源码中找找答案,全局在openjdk8的源码中搜索OmitStackTraceInFastThrow关键字,确实得到了想要的答案,一起来看下。
结合网上的一些结论和源码来看只有以下几类异常才会触发OmitStackTraceInFastThrow,分别是NullPointerException、ArithmeticException、ArrayIndexOutOfBoundsException、ArrayStoreException、ClassCastException,最终发现是有一个脚本文件的内容为空,会触发jsqlparse发生ArrayIndexOutOfBoundsException,进而触发了OmitStackTraceInFastThrow特性,导致工具代码中e.getMessage()返回null而触发NPE造成工具停止运行的假象。
修复办法
-
使用-XX:-OmitStackTraceInFastThrow关闭这一特性;
-
对执行逻辑优化,如果发现脚本文件内容为空就直接返回,不再继续执行;
推荐阅读
https://opts.console.heapdump.cn/result/query/Ex13k
https://heapdump.cn/topic/OmitStackTraceInFastThrow