NoSuchMethodError 常見緣由及解決方法

導讀

『StabilityGuide』是阿里多位阿里技術工程師共同發起的穩定性領域的知識庫開源項目,涵蓋性能壓測、故障演練、JVM、應用容器、服務框架、流量調度、監控、診斷等多個技術領域,以更結構化的方式來打造穩定性領域的知識庫,歡迎您的加入。java

運行時拋出 NoSuchMethodError 的根本緣由是什麼?

在實際生產系統中,咱們主要關注運行時拋出的 NoSuchMethodError 錯誤,該錯誤輕則致使程序異常終止,嚴重時甚至會產生不可預知的程序結果,好比支付服務執行異常,實際支付已完成,卻向用戶返回支付失敗。app

運行時拋出 NoSuchMethodError 錯誤的根本緣由就是: 應用程序直接或間接依賴了同一個類的多個版本,而且在運行時執行了缺乏方法的版本。 以下圖所示:框架

所以,核心問題就轉化爲: 同一類爲何會有多個版本?哪一個版本的類最終會被執行?ide

爲何同一個 Class 會出現多個版本?

致使 Java Class 出現多版本的緣由,能夠概括爲如下幾類:工具

  • JDK 版本不一致。 常見於編譯打包環境使用高版本 JDK 開發與打包,而實際運行環境的 JDK 版本較低。例如,本地項目環境 JDK 版本爲 1.7,調用 Character.isAlphabetic() 方法判斷當前字符是否爲字母;而線上環境 JDK 版本爲 1.6,在運行期間就會拋出 NoSuchMethodError 錯誤。
  • SNAPSHOT 版本不一致。 常見於本地更新 SNAPSHOT 版本後,沒有執行 mvn clean deploy 部署,致使線上環境運行時仍然引用了舊版本的 SNAPSHOT 包。
  • Maven 依賴生命週期爲 provided。 常見於本地依賴的某組件生命週期爲 provided,所聲明版本僅用於本地編譯打包,而線上運行時會經過其餘依賴關係加載 Jar 包。
  • 同一個 Jar 包出現了多個版本。 常見於 Maven 依賴未顯式指定版本號,致使間接依賴版本衝突,很容易引入低版本的 Jar 包。
  • 同一個 Class 出如今不一樣的 Jar 包中。 該問題常見於代碼拷貝場景,好比基於開源版本定製了一些功能,使用了新的 Maven 座標打包發佈,此時 Maven 仲裁機制失效(很是隱蔽,難以排查)。因爲 JVM 類加載器對於同一個類只會加載一次,最終加載的類實現受到 Jar 包依賴的路徑、類聲明的前後順序或文件加載順序等因素的影響,極可能出現不一樣機器加載的類實現不一致。

哪一個版本的 Class 最終會被執行?

影響 Class 最終是否被執行的關鍵因素有兩個:Maven 依賴仲裁機制和 JVM 類加載機制,以下圖所示:性能

首先,Maven 依賴仲裁機制 決定了打包的優先級, 仲裁優先級「從高到低」以下所述:測試

  • 優先按照依賴管理 [dependencyManagement] 元素中指定的版本進行仲裁;
  • 若無版本聲明,則按照 「短路徑優先」 原則(Maven2.0)進行仲裁,即選擇依賴樹中路徑最短的版本;
  • 若路徑長度一致,則按照 「第一聲明優先」 原則進行仲裁,即選擇 POM 中最早聲明的版本。

合理使用 Maven 依賴仲裁機制能夠便捷的管理 Jar 包版本,而不合理的使用將致使多版本 Jar 衝突。ui

其次,JVM 類加載機制 決定了 Class 被加載到 JVM 的優先級, 若是同一個類出如今多個 Jar 包中,那麼在雙親委派類加載機制下,加載該 Jar 包的類加載器層級越高,該 Jar 包越先被加載,它所包含的 Class 越先被執行,如上圖所示:spa

  • 啓動類加載器(Bootstrap ClassLoader)優先級最高,主要加載 JVM 運行時核心類,如 java.util、java.io等,這些類主要位於 $JAVA_HOME/lib/rt.jar 文件中。
  • 擴展類加載器(Extention ClassLoader)優先級次之,主要加載 JVM 擴展類,如 swing 組件、xml 解析器等,這些類主要位於 $JAVA_HOME/lib/ext/ 目錄下的 Jar 包中。
  • 應用類加載器(Application ClassLoader),又稱系統類加載器,優先級再次之,它會加載 Classpath 環境變量裏定義的路徑中的 Jar 包和目錄,一般咱們本身編寫的代碼或依賴的第三方 Jar 包都是由它來加載。

除了上述兩種緣由外,在同一個 ClassLoader 下,若是存在一個 Class 出如今不一樣的 Jar 包中,那麼文件系統的文件加載順序也可能會影響最終的加載結果。所以,應該儘可能保證開發/測試/生產系統環境一致性。日誌

如何解決 NoSuchMethodError 錯誤?

雖然拋出 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 包衝突問題

本文介紹的 Jar 包衝突解決方法,除了解決 java.lang.NoSuchMethodError 之外,對其餘類似問題也具有必定的參考價值。

例如 java.lang.ClassNotFoundException,即加載不到指定類,一般是 Maven 仲裁選錯了版本,如本地開發階段調用了 1.2.0 版本,而打包時採用了 1.0.0 版本的 Jar 包。同理,java.lang.NoClassDefFoundError 和 java.lang.LinkageError 也能夠基於上述思路進行排查。

此外,若是類和方法名都保持不變,可是內部實現有變化,在多版本衝突場景下,不會拋出異常,但程序行爲跟預期不一致, 此時,也能夠基於上述思路進行排查診斷。


原文連接 本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索