最近線上應用一直LOAD值非常高,幾乎接近宕機的邊緣,開始報異常如下:
- at java.util.regex.Pattern$GroupTail.match(Unknown Source)
- at java.util.regex.Pattern$Ctype.match(Unknown Source)
- at java.util.regex.Pattern$Branch.match(Unknown Source)
- at java.util.regex.Pattern$GroupHead.match(Unknown Source)
- at java.util.regex.Pattern$Loop.match(Unknown Source)
- at java.util.regex.Pattern$GroupTail.match(Unknown Source)
- at java.util.regex.Pattern$Ctype.match(Unknown Source)
- at java.util.regex.Pattern$Branch.match(Unknown Source)
- at java.util.regex.Pattern$GroupHead.match(Unknown Source)
- at java.util.regex.Pattern$Loop.match(Unknown Source)
- at java.util.regex.Pattern$GroupTail.match(Unknown Source)
- at java.util.regex.Pattern$Ctype.match(Unknown Source)
- at java.util.regex.Pattern$Branch.match(Unknown Source)
- at java.util.regex.Pattern$GroupHead.match(Unknown Source)
- at java.util.regex.Pattern$Loop.match(Unknown Source)
- at java.util.regex.Pattern$GroupTail.match(Unknown Source)
- at java.util.regex.Pattern$Ctype.match(Unknown Source)
- at java.util.regex.Pattern$Branch.match(Unknown Source)
- at java.util.regex.Pattern$GroupHead.match(Unknown Source)
- at java.util.regex.Pattern$Loop.match(Unknown Source)
- at java.util.regex.Pattern$GroupTail.match(Unknown Source)
- at java.util.regex.Pattern$Ctype.match(Unknown Source)
- at java.util.regex.Pattern$Branch.match(Unknown Source)
通過異常信息抓取定位到我們的一個工具方法:該工具方法如下:
- public static boolean checkSpecialChars(String inputstr, String regex)
- {
- if (inputstr == null || "".equals(inputstr))
- {
- return false;
- }
- return Pattern.compile(regex).matcher(inputstr).matches();
- }
沒有任何地方是通過循環的調用本方法的,但根據異常信息很明顯是死循環,這就引起我們進一步去跟蹤問題,通過一段時間的測試和總結,終於找到問題的產生原因。該方法允許傳一個正則表達式進去, 問題就出在傳入的正則表達式上,該表達式簡化為如下:
- String regex = "([a-z]|//d)*";
通過測試發現,此時若輸入的字符串裡面匹配次數超過817次以後,該方法將變的不穩定,開始重現我們前面的異常信息。測試代碼如下:
- import java.util.regex.Pattern;
- /**
- * Created on 2010-11-9
- * <p>Title: 測試正則表達式死循環</p>
- * @author [email protected]
- * @version 1.0
- */
- public class RegexTest
- {
- public static void main(String args[])
- {
- String regex = "([a-z]|//d)*";
- String inputStr = "";
- for (int i = 0; i < 309; i++) //此處的值為>=400則會馬上拋異常
- {
- inputStr = inputStr.concat(String.valueOf(i)); //循環的拼接輸入字符串
- }
- System.out.println("字符串長度為:"+inputStr.length());
- boolean flag = checkSpecialChars(inputStr, regex);
- System.out.println("匹配結果為: "+flag);
- }
- public static boolean checkSpecialChars(String inputstr, String regex)
- {
- if (inputstr == null || "".equals(inputstr))
- {
- return false;
- }
- return Pattern.compile(regex).matcher(inputstr).matches(); //注意是此處matches()方法拋的異常
- }
- }
原來:該問題是JDK的BUG,到JDK1.6裡居然還沒修復,BUG詳情見:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5050507 和 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6988218
附:也可以參考 http://www.linuxidc.com/Linux/2011-11/46616.htm 這篇文章,非常不錯。
通過上面方法解決上拋異常問題,修改完機器重啟後發現異常是不拋了,但CPU占用率高並沒有好轉,頻頻報警,經過仔細排查,有五個處理正則的線程把CPU資源耗完了,實在沒招,最後校驗采用其它方法,徹底干掉正則。
總結:通過這次線上問題排查,正則表達式是個雙仞劍,如果大規模數據的校驗最好不要使用正則,效率非常差。CPU的處理能力會全部耗費在處理這幾個正則上。另外該問題是項目上線一段時間後才出現,這說明當數據達到一個數量級後,正則的處理效率會快速下降,這樣就像我這種情況,剛開始數據量小,一直沒有問題,等到訪問量突然增大後,CPU在短時間內LOAD值非常高。所以正則輕易不要用在大數據量或者並發訪問較高的應用中。