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