你真的知道 NoSuchMethodError 发生原因和解决办法吗
點擊上方“朱小廝的博客”,選擇“設(shè)為星標(biāo)”
后臺回復(fù)”加群“加入公眾號專屬技術(shù)群
來源:阿里巴巴中間件
當(dāng)應(yīng)用程序試圖調(diào)用類(靜態(tài)或?qū)嵗?#xff09;的指定方法,而該類已不再具有該方法的定義時,就會拋出 java.lang.NoSuchMethodError 錯誤。簡單地說,就是同一個 Class 有多個版本的實現(xiàn),并且在運行時調(diào)用了缺少方法的那個版本。本文總結(jié)了 NoSuchMethodError 常見原因及其解決方法,如有遺漏或錯誤,歡迎補充指正。
運行時拋出 NoSuchMethodError 的根本原因是什么?
在實際生產(chǎn)系統(tǒng)中,我們主要關(guān)注運行時拋出的 NoSuchMethodError 錯誤,該錯誤輕則導(dǎo)致程序異常終止,嚴(yán)重時甚至?xí)a(chǎn)生不可預(yù)知的程序結(jié)果,比如支付服務(wù)執(zhí)行異常,實際支付已完成,卻向用戶返回支付失敗。 運行時拋出 NoSuchMethodError 錯誤的根本原因就是:應(yīng)用程序直接或間接依賴了同一個類的多個版本,并且在運行時執(zhí)行了缺少方法的版本。如下圖所示:
因此,核心問題就轉(zhuǎn)化為:同一類為什么會有多個版本?哪個版本的類最終會被執(zhí)行?
為什么同一個 Class 會出現(xiàn)多個版本?
導(dǎo)致 Java Class 出現(xiàn)多版本的原因,可以歸納為以下幾類:
- JDK 版本不一致。常見于編譯打包環(huán)境使用高版本 JDK 開發(fā)與打包,而實際運行環(huán)境的 JDK 版本較低。例如,本地項目環(huán)境 JDK 版本為 1.7,調(diào)用 Character.isAlphabetic() 方法判斷當(dāng)前字符是否為字母;而線上環(huán)境 JDK 版本為 1.6,在運行期間就會拋出 NoSuchMethodError 錯誤。
- SNAPSHOT 版本不一致。常見于本地更新 SNAPSHOT 版本后,沒有執(zhí)行 mvn clean deploy 部署,導(dǎo)致線上環(huán)境運行時仍然引用了舊版本的 SNAPSHOT 包。
- Maven 依賴生命周期為 provided。常見于本地依賴的某組件生命周期為 provided,所聲明版本僅用于本地編譯打包,而線上運行時會通過其他依賴關(guān)系加載 Jar 包。
- 同一個 Jar 包出現(xiàn)了多個版本。常見于 Maven 依賴未顯式指定版本號,導(dǎo)致間接依賴版本沖突,很容易引入低版本的 Jar 包。
- 同一個 Class 出現(xiàn)在不同的 Jar 包中。該問題常見于代碼拷貝場景,比如基于開源版本定制了一些功能,使用了新的 Maven 坐標(biāo)打包發(fā)布,此時 Maven 仲裁機制失效(非常隱蔽,難以排查)。由于 JVM 類加載器對于同一個類只會加載一次,最終加載的類實現(xiàn)受到 Jar 包依賴的路徑、類聲明的先后順序或文件加載順序等因素的影響,很可能出現(xiàn)不同機器加載的類實現(xiàn)不一致。
哪個版本的 Class 最終會被執(zhí)行?
影響 Class 最終是否被執(zhí)行的關(guān)鍵因素有兩個:Maven 依賴仲裁機制和 JVM 類加載機制,如下圖所示:
首先,Maven 依賴仲裁機制 決定了打包的優(yōu)先級, 仲裁優(yōu)先級“從高到低”如下所述:
優(yōu)先按照依賴管理 [dependencyManagement] 元素中指定的版本進行仲裁;
若無版本聲明,則按照 “短路徑優(yōu)先” 原則(Maven2.0)進行仲裁,即選擇依賴樹中路徑最短的版本;
若路徑長度一致,則按照 “第一聲明優(yōu)先” 原則進行仲裁,即選擇 POM 中最先聲明的版本。
合理使用 Maven 依賴仲裁機制可以便捷的管理 Jar 包版本,而不合理的使用將導(dǎo)致多版本 Jar 沖突。 其次,JVM 類加載機制 決定了 Class 被加載到 JVM 的優(yōu)先級, 如果同一個類出現(xiàn)在多個 Jar 包中,那么在 雙親委派類加載機制 下,加載該 Jar 包的類加載器層級越高,該 Jar 包越先被加載,它所包含的 Class 越先被執(zhí)行,如上圖所示:
啟動類加載器(Bootstrap ClassLoader)優(yōu)先級最高,主要加載 JVM 運行時核心類,如 java.util、java.io等,這些類主要位于 $JAVA_HOME/lib/rt.jar 文件中。
擴展類加載器(Extention ClassLoader)優(yōu)先級次之,主要加載 JVM 擴展類,如 swing 組件、xml 解析器等,這些類主要位于 $JAVA_HOME/lib/ext/ 目錄下的 Jar 包中。
應(yīng)用類加載器(Application ClassLoader),又稱系統(tǒng)類加載器,優(yōu)先級再次之,它會加載 Classpath 環(huán)境變量里定義的路徑中的 Jar 包和目錄,通常我們自己編寫的代碼或依賴的第三方 Jar 包都是由它來加載。
如何解決 NoSuchMethodError 錯誤?
雖然拋出 NoSuchMethodError 錯誤的原因多種多樣,但本質(zhì)上是由于編譯時類路徑與運行時類路徑不一致。因此,通用的定位思路可以歸納為以下 3 步: 1、定位異常 Class 的全限定類名與調(diào)用方,通常可以在應(yīng)用日志拋出的異常堆棧中獲取。如下圖所示: 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、根據(jù) ClassLoader 和 Jar 包全路徑名等信息,判斷是類加載、Maven 仲裁或其他原因,并對應(yīng)的加以解決。例如加載了同一個 Jar 包的低版本實現(xiàn),則在 Maven dependencyManagement 中指定版本,或者移除間接依賴中的低版本(提示: 執(zhí)行 mvn dependency:tree 命令,可以輸出 Maven 依賴拓?fù)潢P(guān)系)。
其他 Jar 包沖突問題
本文介紹的 Jar 包沖突解決方法,除了解決 java.lang.NoSuchMethodError 以外,對其他相似問題也具備一定的參考價值。
例如 java.lang.ClassNotFoundException,即加載不到指定類,通常是 Maven 仲裁選錯了版本,如本地開發(fā)階段調(diào)用了 1.2.0 版本,而打包時采用了 1.0.0 版本的 Jar 包。同理,java.lang.NoClassDefFoundError 和 java.lang.LinkageError 也可以基于上述思路進行排查。
此外,如果類和方法名都保持不變,但是內(nèi)部實現(xiàn)有變化,在多版本沖突場景下,不會拋出異常,但程序行為跟預(yù)期不一致, 此時,也可以基于上述思路進行排查診斷。
參考文章
重新看待 Jar 包沖突問題及解決方案
http://www.yangbing.club/2017/07/15/solution-for-jar-conflicts/
3 Steps to Fix NoSuchMethodErrors and NoSuchMethodExceptionshttps://reflectoring.io/nosuchmethod/
想知道更多?掃描下面的二維碼關(guān)注我
免費資料入口:后臺回復(fù)“666”
總結(jié)
以上是生活随笔為你收集整理的你真的知道 NoSuchMethodError 发生原因和解决办法吗的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你知道SQL的这些错误用法吗?
- 下一篇: 96秒100亿!如何抗住双11高并发流量