『StabilityGuide』是阿里多位阿里技術工程師共同發起的穩定性領域的知識庫開源項目,涵蓋性能壓測、故障演練、JVM、應用容器、服務框架、流量調度、監控、診斷等多個技術領域,以更結構化的方式來打造穩定性領域的知識庫,歡迎您的加入。java
在實際生產系統中,咱們主要關注運行時拋出的 NoSuchMethodError 錯誤,該錯誤輕則致使程序異常終止,嚴重時甚至會產生不可預知的程序結果,好比支付服務執行異常,實際支付已完成,卻向用戶返回支付失敗。app
運行時拋出 NoSuchMethodError 錯誤的根本緣由就是: 應用程序直接或間接依賴了同一個類的多個版本,而且在運行時執行了缺乏方法的版本。 以下圖所示:框架
所以,核心問題就轉化爲: 同一類爲何會有多個版本?哪一個版本的類最終會被執行?ide
致使 Java Class 出現多版本的緣由,能夠概括爲如下幾類:工具
影響 Class 最終是否被執行的關鍵因素有兩個:Maven 依賴仲裁機制和 JVM 類加載機制,以下圖所示:性能
首先,Maven 依賴仲裁機制 決定了打包的優先級, 仲裁優先級「從高到低」以下所述:測試
合理使用 Maven 依賴仲裁機制能夠便捷的管理 Jar 包版本,而不合理的使用將致使多版本 Jar 衝突。ui
其次,JVM 類加載機制 決定了 Class 被加載到 JVM 的優先級, 若是同一個類出如今多個 Jar 包中,那麼在雙親委派類加載機制下,加載該 Jar 包的類加載器層級越高,該 Jar 包越先被加載,它所包含的 Class 越先被執行,如上圖所示:spa
除了上述兩種緣由外,在同一個 ClassLoader 下,若是存在一個 Class 出如今不一樣的 Jar 包中,那麼文件系統的文件加載順序也可能會影響最終的加載結果。所以,應該儘可能保證開發/測試/生產系統環境一致性。日誌
雖然拋出 NoSuchMethodError 錯誤的緣由多種多樣,但本質上是因爲編譯時類路徑與運行時類路徑不一致。所以,通用的定位思路能夠概括爲如下 3 步:
一、定位異常 Class 的全限定類名與調用方,一般能夠在應用日誌拋出的異常堆棧中獲取。以下圖所示:
Exception in thread "main" java.lang.NoSuchMethodError: com.xxx.AsyncAppender.append(Ljava/lang/String;)Ljava/lang/String; at com.xxx.ProvokeNoSuchMethodError.main(ProvokeNoSuchMethodError:7) at ……
二、定位異常 Class 的來源,能夠經過 Arthas 等在線診斷工具反編譯,如 jad com.xxx.AsyncAppender,獲取該類運行時的源碼、ClassLoader、Jar 包位置等信息。
若是應用程序啓動失敗,或者沒法進行在線診斷,能夠考慮添加 JVM 啓動參數 -verbose:class 或 -XX:+TraceClassLoading,在日誌中將輸出每一個類的加載信息,好比來自哪一個 Jar 包。
三、根據 ClassLoader 和 Jar 包全路徑名等信息,判斷是類加載、Maven 仲裁或其餘緣由,並對應的加以解決。
若是是同一個 Jar 包的多版本問題,能夠在 Maven 標籤中指定實際須要的版本,或者移除間接依賴中的低版本(提示: 執行 mvn dependency:tree 命令,能夠查看 Maven 依賴拓撲關係)。
若是是同一個 Class 出如今不一樣的 Jar 包問題,若能夠排除,就用 排除該依賴;如不能排除,則考慮升級或替換爲其餘 Jar 包,或者考慮使用 ClassLoader 隔離技術,可參考 《若是jar包衝突不可避免,如何實現jar包隔離?》。
本文介紹的 Jar 包衝突解決方法,除了解決 java.lang.NoSuchMethodError 之外,對其餘類似問題也具有必定的參考價值。
例如 java.lang.ClassNotFoundException,即加載不到指定類,一般是 Maven 仲裁選錯了版本,如本地開發階段調用了 1.2.0 版本,而打包時採用了 1.0.0 版本的 Jar 包。同理,java.lang.NoClassDefFoundError 和 java.lang.LinkageError 也能夠基於上述思路進行排查。
此外,若是類和方法名都保持不變,可是內部實現有變化,在多版本衝突場景下,不會拋出異常,但程序行爲跟預期不一致, 此時,也能夠基於上述思路進行排查診斷。
原文連接 本文爲雲棲社區原創內容,未經容許不得轉載。