Tomcat啟動時檢測到循環(huán)繼承而棧溢出的問題

一個用戶在使用tomcat7054版本啟動的時候遇到的錯誤:

Caused by: java.lang.IllegalStateException: 
Unable to complete the scan for annotations for web application [/test] 
due to a StackOverflowError. Possible root causes include a too low setting 
for  -Xss and illegal cyclic inheritance dependencies. 
The class hierarchy being processed was 
[org.jaxen.util.AncestorAxisIterator->
org.jaxen.util.AncestorOrSelfAxisIterator->
org.jaxen.util.AncestorAxisIterator]
at org.apache.catalina.startup.ContextConfig.checkHandlesTypes(ContextConfig.java:2112)
at org.apache.catalina.startup.ContextConfig.processAnnotationsStream(ContextConfig.java:2059)
at org.apache.catalina.startup.ContextConfig.processAnnotationsJar(ContextConfig.java:1934)
at org.apache.catalina.startup.ContextConfig.processAnnotationsUrl(ContextConfig.java:1900)
at org.apache.catalina.startup.ContextConfig.processAnnotations(ContextConfig.java:1885)
at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1317)
at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:876)
at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:374)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5355)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)

這是在tomcat解析servlet3注釋時進(jìn)行類掃描的過程航揉,發(fā)現(xiàn)了兩個類的繼承關(guān)系存在循環(huán)繼承的情況而導(dǎo)致了棧溢出奕筐。排查了一下,是因?yàn)閼?yīng)用所依賴的 dom4j-1.1.jar 里存在 AncestorAxisIterator 和子類 AncestorOrSelfAxisIterator:

% javap org.jaxen.util.AncestorAxisIterator

Compiled from "AncestorAxisIterator.java"
public class org.jaxen.util.AncestorAxisIterator extends org.jaxen.util.StackedIterator {
    protected org.jaxen.util.AncestorAxisIterator();
    public org.jaxen.util.AncestorAxisIterator(java.lang.Object, org.jaxen.Navigator);
    protected java.util.Iterator createIterator(java.lang.Object);
}

% javap org.jaxen.util.AncestorOrSelfAxisIterator

Compiled from "AncestorOrSelfAxisIterator.java"
public class org.jaxen.util.AncestorOrSelfAxisIterator extends org.jaxen.util.AncestorAxisIterator {
    public org.jaxen.util.AncestorOrSelfAxisIterator(java.lang.Object, org.jaxen.Navigator);
    protected java.util.Iterator createIterator(java.lang.Object);
} 

同時應(yīng)用所依賴的 sourceforge.jaxen-1.1.jar 里面也存在這兩個同名類,但繼承關(guān)系正好相反:

% javap org.jaxen.util.AncestorAxisIterator

Compiled from "AncestorAxisIterator.java"
public class org.jaxen.util.AncestorAxisIterator extends org.jaxen.util.AncestorOrSelfAxisIterator {
    public org.jaxen.util.AncestorAxisIterator(java.lang.Object, org.jaxen.Navigator);
}

% javap org.jaxen.util.AncestorOrSelfAxisIterator

Compiled from "AncestorOrSelfAxisIterator.java"
public class org.jaxen.util.AncestorOrSelfAxisIterator implements java.util.Iterator {
    public org.jaxen.util.AncestorOrSelfAxisIterator(java.lang.Object, org.jaxen.Navigator);
    public boolean hasNext();
    public java.lang.Object next();
    public void remove();
}

簡單的說麻诀,在第1個jar里存在 B繼承自A献酗,在第2個jar里存在同名的A和B,但卻是A繼承自B偎窘。其實(shí)也能運(yùn)行的乌助,只是可能出現(xiàn)類加載時可能加載的不一定是你想要的那個,但tomcat做類型檢查的時候把這個當(dāng)成了一個環(huán)陌知。

在ContextConfig.processAnnotationsStream方法里他托,每次解析之后要對類型做一次檢測,然后才獲取注釋信息:

ClassParser parser = new ClassParser(is, null);
JavaClass clazz = parser.parse();
checkHandlesTypes(clazz);
...
AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
...

再看這個用來檢測類型的checkHandlesTypes方法里面:

populateJavaClassCache(className, javaClass);
JavaClassCacheEntry entry = javaClassCache.get(className);
if (entry.getSciSet() == null) {
    try {
        populateSCIsForCacheEntry(entry); // 這里
    } catch (StackOverflowError soe) {
        throw new IllegalStateException(sm.getString(
            "contextConfig.annotationsStackOverflow",context.getName(),
            classHierarchyToString(className, entry)));
    }
}

每次新解析出來的類(tomcat里定義了JavaClass來描述)仆葡,會被populateJavaClassCache放入cache赏参,這個cache內(nèi)部是個Map,所以對于key相同的會存在把以前的值覆蓋了的情況沿盅,這個“環(huán)形繼承”的現(xiàn)象就比較好解釋了把篓。

Map里的key是String類型即類名,value是JavaClassCacheEntry類型封裝了JavaClass及其父類和接口信息腰涧。我們假設(shè)第一個jar里B繼承自A纸俭,它們被放入cache的時候鍵值對是這樣的:

"A" -> [JavaClass-A, 父類Object,父接口]"
"B" -> [JavaClass-B, 父類A南窗,父接口]

然后當(dāng)解析到第2個jar里的A的時候揍很,覆蓋了之前A的鍵值對郎楼,變成了:

"A" -> [JavaClass-A, 父類B,父接口]
"B" -> [JavaClass-B, 父類A窒悔,父接口]

這2個的繼承關(guān)系在這個cache里被描述成了環(huán)狀呜袁,然后在接下來的populateSCIsForCacheEntry方法里找父類的時候就繞不出來了,最終導(dǎo)致了棧溢出简珠。

這個算是cache設(shè)計(jì)不太合理阶界,沒有考慮到不同jar下面有相同的類的情況。問題確認(rèn)之后聋庵,讓應(yīng)用方去修正自己的依賴就可以了膘融,但應(yīng)用方說之前在7026的時候,是可以正常啟動的祭玉。這就有意思了氧映,接著一番排查之后,發(fā)現(xiàn)在7026版本里脱货,ContextConfig.webConfig的時候先判斷了一下web.xml里的版本信息岛都,如果版本>=3才會去掃描類里的servlet3注釋信息。

// Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();
parseWebXml(contextWebXml, webXml, false);

if (webXml.getMajorVersion() >= 3) {
    // 掃描jar里的web-fragment.xml 和 servlet3注釋信息
    ...
}

而在7054版本里是沒有這個判斷的振峻。搜了一下臼疫,發(fā)現(xiàn)是在7029這個版本里去掉的這個判斷。在7029的changelog里:

As per section 1.6.2 of the Servlet 3.0 specification and clarification from the Servlet Expert Group, the servlet specification version declared in web.xml no longer controls if >Tomcat scans for annotations. Annotation scanning is now always performed – regardless of the version declared in web.xml – unless metadata complete is set to true.

之前對servlet3規(guī)范理解不夠清晰扣孟;之所以改烫堤,是因?yàn)樵趙eb.xml里定義的servlet版本,不再控制tomcat是否去掃描每個類里的注釋信息凤价。也就是說不管web.xml里聲明的servlet版本是什么鸽斟,都會進(jìn)行注釋掃描,除非metadata-complete屬性設(shè)置為true(默認(rèn)是false)料仗。

所以在7029版本之后改為了判斷 webXml.isMetadataComplete() 是否需要進(jìn)行掃描注釋信息湾盗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市立轧,隨后出現(xiàn)的幾起案子格粪,更是在濱河造成了極大的恐慌,老刑警劉巖氛改,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帐萎,死亡現(xiàn)場離奇詭異,居然都是意外死亡胜卤,警方通過查閱死者的電腦和手機(jī)疆导,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葛躏,“玉大人澈段,你說我怎么就攤上這事悠菜。” “怎么了败富?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵悔醋,是天一觀的道長。 經(jīng)常有香客問我兽叮,道長芬骄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任鹦聪,我火速辦了婚禮账阻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泽本。我一直安慰自己淘太,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布观挎。 她就那樣靜靜地躺著琴儿,像睡著了一般段化。 火紅的嫁衣襯著肌膚如雪嘁捷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天显熏,我揣著相機(jī)與錄音雄嚣,去河邊找鬼。 笑死喘蟆,一個胖子當(dāng)著我的面吹牛缓升,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蕴轨,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼港谊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了橙弱?” 一聲冷哼從身側(cè)響起歧寺,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棘脐,沒想到半個月后斜筐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛀缝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年顷链,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屈梁。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡嗤练,死狀恐怖榛了,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情煞抬,我是刑警寧澤忽冻,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站此疹,受9級特大地震影響僧诚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蝗碎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一湖笨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蹦骑,春花似錦慈省、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捎废,卻和暖如春笑窜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背登疗。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工排截, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辐益。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓断傲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親智政。 傳聞我的和親對象是個殘疾皇子认罩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容