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 也可以基于上述思路進行排查。
此外,如果類和方法名都保持不變,但是內部實現有變化,在多版本沖突場景下,不會拋出異常,但程序行為跟預期不一致, 此時,也可以基于上述思路進行排查診斷。
推薦工具&產品
ARMS —— 阿里云 APM 產品,支持 NoSuchMethodError 異常關鍵字告警
https://help.aliyun.com/document_detail/42966.html
Arthas —— Java 在線診斷工具
https://github.com/alibaba/arthas
參考文章
重新看待 Jar 包沖突問題及解決方案
http://www.yangbing.club/2017/07/15/solution-for-jar-conflicts/
3 Steps to Fix NoSuchMethodErrors and NoSuchMethodExceptionshttps://reflectoring.io/nosuchmethod/
作者信息:
夏明,GitHub ID @StabilityMan,花名涯海,阿里云 ARMS & EagleEye 技術專家,2016 年加入阿里巴巴,一直從事鏈路追蹤和 APM 監控診斷領域的相關工作。
本文縮略圖:icon by 軒坊
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的NoSuchMethodError 常见原因及解决方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何插入? | 非开车,纯技术交流
- 下一篇: NYOJ 1186 心理阴影(两个圆环的