聊聊 Java Regex StackOverflowError 問題及其最佳化

來源:互聯網
上載者:User

標籤:

正則可以看做一門 DSL,但它卻應用極其廣泛,可以輕鬆解決很多情境下的字串匹配、篩選問題。同時呢有句老話:

“ 如果你有一個問題,用Regex解決,那麼你現在就有兩個問題了。”

Some people, when confronted with a problem, think "I know, I‘ll use regular expressions." Now they have two problems.

今天我們就來聊聊 Java Regex StackOverflowError 的問題及其一些最佳化點。

1、問題

最近,有同事發現一段正則在本地怎麼跑都沒問題,但是放到 Hadoop 叢集上總會時不時的拋 StackOverflowError 。

代碼我先簡化下:

package java8test;import java.util.regex.Matcher;import java.util.regex.Pattern;public class Test {public static void main(String[] args) {final String TEST_REGEX = "([=+]|[\\s]|[\\p{P}]|[A-Za-z0-9]|[\u4E00-\u9FA5])+";StringBuilder line = new StringBuilder();System.out.println("++++++++++++++++++++++++++++++");for (int i = 0; i < 10; i++) {line.append("http://hh.ooxx.com/ershoufang/?PGTID=14366988648680=+.7342327926307917&ClickID=1&key=%2525u7261%2525u4E39%2525u5BCC%2525u8D35%2525u82B1%2525u56ED&sourcetype=1_5");line.append("http://wiki.corp.com/index.php?title=Track%E6%A0%87%E5%87%86%E6%97%A5%E5%BF%97Hive%E8%A1%A8-%E5%8D%B3%E6%B8%85%E6%B4%97%E5%90%8E%E7%9A%84%E6%97%A5%E5%BF%97");line.append("http://www.baidu.com/s?ie=UTF-8&wd=58%cd%ac%b3%c7%b6%fe%ca%d6%b3%b5%b2%e2%ca%d4%ca%fd%be%dd&tn=11000003_hao_dg");line.append("http://cs.ooxx.com/yewu/?key=城&cmcskey=的設計費開始低&final=1&jump=1&specialtype=gls");line.append("http%3A%2F%2Fcq.ooxx.com%2Fjob%2F%3Fkey%3D%25E7%25BD%2591%25E4%25B8%258A%25E5%2585%25BC%25E8%2581%258C%26cmcskey%3D%25E7%25BD%2591%25E4%25B8%258A%25E5%2585%25BC%25E8%2581%258C%26final%3D1%26jump%3D2%26specialtype%3Dgls%26canclequery%3Disbiz%253D0%26sourcetype%3D4");}line.append(" \001 11111111111111111111111111");Pattern p_a = null;try {p_a = Pattern.compile(TEST_REGEX);Matcher m_a = p_a.matcher(line);while (m_a.find()) {String a = m_a.group();System.out.println(a);}} catch (Exception e) {// TODO: handle exception}System.out.println("line size: " + line.length());}}

執行之後的結果是:

++++++++++++++++++++++++++++++Exception in thread "main" java.lang.StackOverflowErrorat java.util.regex.Pattern$Loop.match(Unknown Source)at java.util.regex.Pattern$GroupTail.match(Unknown Source)at java.util.regex.Pattern$BranchConn.match(Unknown Source)at java.util.regex.Pattern$CharProperty.match(Unknown Source)......

起初這個問題是從叢集上拋出來的,大家可以看到這個異常有兩個特點:

(1)不可用 Exception 捕獲,因為 Error 直接繼承自 Throwable 而非 Exception,所以即使你要捕獲也應當捕獲 Error。

(2)另外一點是大家可以看到拋出的錯誤並沒有指明行號,當這段代碼混在一個數百行的工具類,有數十條類似的正則的時候,無疑給定位問題帶來了難度,這就需要我們能有一定的單元測試能力。

註:

(1)如果你的環境沒有拋出上述錯誤,嘗試調大 for 迴圈的次數或者指定 jvm 參數:-Xss1k

(2)如果你還不明白 StackOverflowError 是什麼含義,可以參考上一篇文章:JVM 運行時資料區簡介

2、問題分析

Regex引擎分成兩類,一類稱為DFA(確定性有窮自動機),另一類稱為NFA(非確定性有窮自動機)。兩類引擎要順利工作,都必須有一個正則式和一個文本串。DFA捏著文本串去比較正則式,看到一個子正則式,就把可能的匹配串全標註出來,然後再看正則式的下一個部分,根據新的匹配結果更新標註。而NFA是捏著正則式去比文本,吃掉一個字元,就把它跟正則式比較,匹配就記下來,然後接著往下幹。一旦不匹配,就把剛吃的這個字元吐出來,一個個的吐,直到回到上一次匹配的地方。 

     DFA與NFA機制上的不同帶來5個影響: 

     1. DFA 對於文本串裡的每一個字元只需掃描一次,比較快,但特性較少;NFA要翻來覆去吃字元、吐字元,速度慢,但是特性豐富,所以反而應用廣泛,當今主要的Regex引擎,如Perl、Ruby、Python的re模組、Java和.NET的regex庫,都是NFA的。 

     2. 只有NFA才支援lazy和backreference等特性; 

     3. NFA急於邀功請賞,所以最左子正則式優先匹配成功,因此偶爾會錯過首選結果;DFA則是“最長的左子正則式優先匹配成功”。 

     4. NFA預設採用greedy量詞; 

     5. NFA可能會陷入遞迴調用的陷阱而表現得效能極差。 

在使用Regex的時候,底層是通過遞迴方式調用執行的,每一層的遞迴都會在棧線程的大小中佔一定記憶體,如果遞迴的層次很多,就會報出stackOverFlowError異常。所以在使用正則的時候其實是有利有弊的。

Java程式中,每個線程都有自己的Stack Space。這個Stack Space不是來自Heap的分配。所以Stack Space的大小不會受到-Xmx和-Xms的影響,這2個JVM參數僅僅是影響Heap的大小。Stack Space用來做方法的遞迴調用時壓入Stack Frame。所以當遞迴調用太深的時候,就有可能耗盡Stack Space,爆出StackOverflow的錯誤。Stack Space的大小隨著OS,JVM以及環境變數的大小而發生變化。一般說來預設的大小是512K。在64位的系統中,這個Stack Space值會更大。一般說來,Stack Space為128K是夠用的。這時你說需要做的就是觀察。如果你的程式沒有爆出StackOverflow的錯誤,可以使用-Xss來調整Stack Space的大小為128K。(eg:-Xss128K)

文章開頭的問題可以簡單理解為方法的嵌套調用層次太深,上層的方法棧一直得不到釋放,導致棧空間不足。

下面我們要做的就是瞭解一些正則效能的最佳化點,規避這種深層次的遞迴調用。

3、Java 正則的一些最佳化點3.1 Pattern.compile() 先行編譯運算式

如果在程式中多次使用同一個Regex,一定要用Pattern.compile()編譯,代替直接使用Pattern.matches()。如果一次次對同一個Regex使用Pattern.matches(),例如在迴圈中,沒有編譯的Regex消耗比較大。因為matches()方法每次都會先行編譯使用的運算式。另外,記住你可以通過調用reset()方法對不同的輸入字串重複使用Matcher對象。

3.2 留意選擇(Beware of alternation)

類似“(X|Y|Z)”的Regex有降低速度的壞名聲,所以要多留心。首先,考慮選擇的順序,那麼要將比較常用的選擇項放在前面,因此它們可以較快被匹配。另外,嘗試提取共用模式;例如將“(abcd|abef)”替換為“ab(cd|ef)”。後者匹配速度較快,因為NFA會嘗試匹配ab,如果沒有找到就不再嘗試任何選擇項。(在當前情況下,只有兩個選擇項。如果有很多選擇項,速度將會有顯著的提升。)選擇的確會降低程式的速度。在我的測試中,運算式“.*(abcd|efgh|ijkl).*”要比調用String.indexOf()三次——每次針對錶達式中的一個選項——慢三倍。

3.3 減少分組與嵌套

如果你實際並不需要擷取一個分組內的文本,那麼就使用非捕獲分組。例如使用“(?:X)”代替“(X)”。

總結下來就是:減少分支選擇、減少捕獲嵌套、減少貪婪匹配

4、解決方案4.1 臨時工方案

try...catch.../增加-Xss,治標不治本,不推薦。

4.2 最佳化正則才是王道4.2.1 文法層面最佳化

根據 2.2 提到的,我們這樣最佳化下:

final String TEST_REGEX = "([=+\\s\\p{P}A-Za-z0-9\u4E00-\u9FA5])+";

經測試,JVM 參數不變的情況下,for 迴圈 100w 次直到 OOM 了都不會再發生文章開頭的棧溢出的問題了。

4.2.2 商務邏輯層面最佳化

由於我不清楚作者的業務情境,不好做業務最佳化,總的原則是當你的正則太複雜的時候,可以考慮邏輯拆分,或者部分不走正則,如果把正則當做萬能工具可能會得不償失。

總結:在字串尋找與匹配領域,正則可以說幾乎是“萬能”的,但是許多情境下,它的代價不容小覷,如何寫出高效率、可維護的正則或者怎麼能避開正則都是值得咱們思考的問題。

Refer:

[1] 關於Java正則引起的StackOverFlowError問題以及解決方案

http://blog.csdn.net/qq522935502/article/details/8161273

[2] Java正則與棧溢出

http://daimojingdeyu.iteye.com/blog/385304

[3] 最佳化Java中的Regex

http://blog.csdn.net/mydeman/article/details/1800636

[4] 從一個Regex造成的StackOverflowError說起

http://ren.iteye.com/blog/1828562

[5] Regex(三):Unicode諸問題(下)

http://www.infoq.com/cn/news/2011/03/regular-expressions-unicode-2

http://www.infoq.com/cn/author/%E4%BD%99%E6%99%9F

[6] StackOverflowError when matching large input using RegEx

http://stackoverflow.com/questions/15082010/stackoverflowerror-when-matching-large-input-using-regex

[7] try/catch on stack overflows in java?

http://stackoverflow.com/questions/2535723/try-catch-on-stack-overflows-in-java

[8] Java正則達式引起死迴圈問題解決辦法

http://blog.csdn.net/shixing_11/article/details/5997567

[9] JAVA Regex的溢出問題 及不完全解決方案

http://www.blogjava.net/roymoro/archive/2011/04/28/349163.html

聊聊 Java Regex StackOverflowError 問題及其最佳化

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.