歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Java正則達式引起死循環問題解決辦法

最近線上應用一直LOAD值非常高,幾乎接近宕機的邊緣,開始報異常如下:

  1. at java.util.regex.Pattern$GroupTail.match(Unknown Source)  
  2. at java.util.regex.Pattern$Ctype.match(Unknown Source)  
  3. at java.util.regex.Pattern$Branch.match(Unknown Source)  
  4. at java.util.regex.Pattern$GroupHead.match(Unknown Source)  
  5. at java.util.regex.Pattern$Loop.match(Unknown Source)  
  6. at java.util.regex.Pattern$GroupTail.match(Unknown Source)  
  7. at java.util.regex.Pattern$Ctype.match(Unknown Source)  
  8. at java.util.regex.Pattern$Branch.match(Unknown Source)  
  9. at java.util.regex.Pattern$GroupHead.match(Unknown Source)  
  10. at java.util.regex.Pattern$Loop.match(Unknown Source)  
  11. at java.util.regex.Pattern$GroupTail.match(Unknown Source)  
  12. at java.util.regex.Pattern$Ctype.match(Unknown Source)  
  13. at java.util.regex.Pattern$Branch.match(Unknown Source)  
  14. at java.util.regex.Pattern$GroupHead.match(Unknown Source)  
  15. at java.util.regex.Pattern$Loop.match(Unknown Source)  
  16. at java.util.regex.Pattern$GroupTail.match(Unknown Source)  
  17. at java.util.regex.Pattern$Ctype.match(Unknown Source)  
  18. at java.util.regex.Pattern$Branch.match(Unknown Source)  
  19. at java.util.regex.Pattern$GroupHead.match(Unknown Source)  
  20. at java.util.regex.Pattern$Loop.match(Unknown Source)  
  21. at java.util.regex.Pattern$GroupTail.match(Unknown Source)  
  22. at java.util.regex.Pattern$Ctype.match(Unknown Source)  
  23. at java.util.regex.Pattern$Branch.match(Unknown Source)  

通過異常信息抓取定位到我們的一個工具方法:該工具方法如下:

  1. public static boolean checkSpecialChars(String inputstr, String regex)  
  2. {  
  3.     if (inputstr == null || "".equals(inputstr))  
  4.     {  
  5.         return false;  
  6.     }  
  7.     return Pattern.compile(regex).matcher(inputstr).matches();  
  8. }  

沒有任何地方是通過循環的調用本方法的,但根據異常信息很明顯是死循環,這就引起我們進一步去跟蹤問題,通過一段時間的測試和總結,終於找到問題的產生原因。該方法允許傳一個正則表達式進去, 問題就出在傳入的正則表達式上,該表達式簡化為如下:

  1. String regex = "([a-z]|//d)*";  

通過測試發現,此時若輸入的字符串裡面匹配次數超過817次以後,該方法將變的不穩定,開始重現我們前面的異常信息。測試代碼如下:

  1. import java.util.regex.Pattern;  
  2. /** 
  3.  * Created on 2010-11-9 
  4.  * <p>Title:       測試正則表達式死循環</p> 
  5.  * @author         [email protected] 
  6.  * @version        1.0 
  7.  */  
  8. public class RegexTest  
  9. {  
  10.     public static void main(String args[])  
  11.     {  
  12.         String regex = "([a-z]|//d)*";  
  13.         String inputStr = "";  
  14.         for (int i = 0; i < 309; i++) //此處的值為>=400則會馬上拋異常   
  15.         {  
  16.             inputStr = inputStr.concat(String.valueOf(i));  //循環的拼接輸入字符串   
  17.         }  
  18.         System.out.println("字符串長度為:"+inputStr.length());  
  19.         boolean flag = checkSpecialChars(inputStr, regex);  
  20.         System.out.println("匹配結果為: "+flag);  
  21.     }  
  22.     public static boolean checkSpecialChars(String inputstr, String regex)  
  23.     {  
  24.         if (inputstr == null || "".equals(inputstr))  
  25.         {  
  26.             return false;  
  27.         }  
  28.         return Pattern.compile(regex).matcher(inputstr).matches(); //注意是此處matches()方法拋的異常   
  29.     }  
  30. }  

原來:該問題是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值非常高。所以正則輕易不要用在大數據量或者並發訪問較高的應用中。

Copyright © Linux教程網 All Rights Reserved