背景
之前做的小工具一个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