案例分享-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造成工具停止运行的假象。

修复办法

  1. 使用-XX:-OmitStackTraceInFastThrow关闭这一特性;

  2. 对执行逻辑优化,如果发现脚本文件内容为空就直接返回,不再继续执行;

推荐阅读

https://opts.console.heapdump.cn/result/query/Ex13k

https://heapdump.cn/topic/OmitStackTraceInFastThrow

一个jsqlparse+git做的小工具帮我节省时间摸鱼