NoSuchMethodError 发生原因和解决办法
當應用程序試圖調用類(靜態或實例)的指定方法,而該類已不再具有該方法的定義時,就會拋出 java.lang.NoSuchMethodError 錯誤。簡單地說,就是同一個 Class 有多個版本的實現,并且在運行時調用了缺少方法的那個版本。本文總結了 NoSuchMethodError 常見原因及其解決方法,如有遺漏或錯誤,歡迎補充指正。
?
運行時拋出 NoSuchMethodError 的根本原因是什么?
?
在實際生產系統中,我們主要關注運行時拋出的 NoSuchMethodError 錯誤,該錯誤輕則導致程序異常終止,嚴重時甚至會產生不可預知的程序結果,比如支付服務執行異常,實際支付已完成,卻向用戶返回支付失敗。
?
運行時拋出 NoSuchMethodError 錯誤的根本原因就是:應用程序直接或間接依賴了同一個類的多個版本,并且在運行時執行了缺少方法的版本。如下圖所示:
?
?
因此,核心問題就轉化為:同一類為什么會有多個版本?哪個版本的類最終會被執行?
?
為什么同一個 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 沖突。
?
其次,JVM 類加載機制 決定了 Class 被加載到 JVM 的優先級, 如果同一個類出現在多個 Jar 包中,那么在 雙親委派類加載機制 下,加載該 Jar 包的類加載器層級越高,該 Jar 包越先被加載,它所包含的 Class 越先被執行,如上圖所示:
?
-
啟動類加載器(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 步:
?
1、定位異常 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 ……?
2、定位異常 Class 的來源,可以通過 Arthas 等在線診斷工具反編譯,如 jad com.xxx.AsyncAppender,獲取該類運行時的源碼、ClassLoader、Jar 包位置等信息。
?
?
1、根據 ClassLoader 和 Jar 包全路徑名等信息,判斷是類加載、Maven 仲裁或其他原因,并對應的加以解決。例如加載了同一個 Jar 包的低版本實現,則在 Maven dependencyManagement 中指定版本,或者移除間接依賴中的低版本(提示: 執行 mvn dependency:tree 命令,可以輸出 Maven 依賴拓撲關系)。
?
?
其他 Jar 包沖突問題
?
本文介紹的 Jar 包沖突解決方法,除了解決 java.lang.NoSuchMethodError 以外,對其他相似問題也具備一定的參考價值。
?
例如 java.lang.ClassNotFoundException,即加載不到指定類,通常是 Maven 仲裁選錯了版本,如本地開發階段調用了 1.2.0 版本,而打包時采用了 1.0.0 版本的 Jar 包。同理,java.lang.NoClassDefFoundError 和 java.lang.LinkageError 也可以基于上述思路進行排查。
?
此外,如果類和方法名都保持不變,但是內部實現有變化,在多版本沖突場景下,不會拋出異常,但程序行為跟預期不一致, 此時,也可以基于上述思路進行排查診斷。
?
參考文章
?
-
重新看待 Jar 包沖突問題及解決方案
http://www.yangbing.club/2017/07/15/solution-for-jar-conflicts/
-
3 Steps to Fix NoSuchMethodErrors and NoSuchMethodExceptions
https://reflectoring.io/nosuchmethod/
?
?
作者信息:
夏明,GitHub ID @StabilityMan,花名涯海,阿里云 ARMS & EagleEye 技術專家,2016 年加入阿里巴巴,一直從事鏈路追蹤和 APM 監控診斷領域的相關工作。
?
本文縮略圖:icon by 軒坊
總結
以上是生活随笔為你收集整理的NoSuchMethodError 发生原因和解决办法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spark _15 _广播变量和累加器
- 下一篇: hbase 中的LSM树存储引擎