Arthas 實戰,助你解決同名類依賴沖突問題


上篇文章中,小黑哥分析 Maven 依賴沖突分為兩類:

  • 項目同一依賴應用,存在多版本,每個版本同一個類,可能存在差異。
  • 項目不同依賴應用,存在包名,類名完全一樣的類。

第二種情況,往往是這個場景,本地/測試環境運行的都是好好的,上線之后測試就是不行。

這其實與 JVM 類加載有關,本地/測試環境加載正確類,而生產環節加載錯的類,為什么會這樣?

主要有兩個原因:

  • 同一個類只會被加載器加載一次
  • 不同環境,類的加載順序不同

同一個類只會被加載器加載一次

JVM 類加載具有緩存機制,每個類加載的時候首先檢查一遍,類是否被當前類加載器加載。若未被加載,先交給其父類加載器加載,父類加載器不能加載,才會交給當前類加載器。

當前類加載器加載完成之后,將會將其緩存起來。

圖片來自網絡

類加載的核心源碼位於 ClassLoader#loadClass

① 處將會檢查ClassLoader#findLoadedClass 最終將會調用 ClassLoader#findLoadedClass0,這是一個 native 方法,最終將會根據類名加類加載器為鍵值查找緩存。

每個類加載器負責的加載范圍都不一樣:

  • BootstrapClassLoader 引導類加載加載最核心的類庫,如 $JAVA_HOME/jre/lib/
  • ExtClassLoader 擴展類加載器負責加載$JAVA_HOME/jre/lib/ext下的一些擴展類
  • AppClassLoader 應用類加載器將加載 classpath 指定的類。

我們運行的應用依賴的各種類,一般將會由 AppClassLoader 記載,同名類被加載后,下次碰到就不會再被加載。

畫外音:利用緩存加快查詢速度

不同環境,類的加載順序不同

Java 可以使用 -classpath 參數指定依賴類所在位置。

類的加載順序可以通過以下方式指定:

java -classpath a.jar:b.jar:c.jar xx.xx.Main

上面這種方式,類加載首先會從 a.jar 中查找相關類,找不到才會繼續往后查找。所以可以通過這種方式可以指定使用哪個 jar 包內同名類。

但是這種方式有點繁瑣,如果依賴 100 個 jar 包,需要全部寫上去。

所以生產環境可以使用使用 shell 命令將 jar 拼接起來:

LIB_DIR=lib
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"`

另外 java 支持通配符的寫法:

java -classpath './*' xx.xx.Main

這種方式的加載順序將會受到底層系統文件加載順序影響。

復現依賴沖突

假設我們現在應用依賴如下:

A 應用依賴 B、C,且 B,C 中存在同包同名類 org.example.App,代碼如下:

如果指定 jar 包順序啟動應用:

# A,B,C 放置同一文件夾下
java -classpath A-1.0-SNAPSHOT.jar:B-1.0-SNAPSHOT.jar:C-1.0-SNAPSHOT.jar org.example.ClassA

日志輸出如下:

改變 B ,C 順序:

類加載器的類的查找順序將會通過 classpath 指定順序從前往后查找。

如果使用通配符啟動:

java -classpath './*' org.example.ClassA

這種情況 jvm 到底加載那個類就成了薛定諤的了,運行之前無法確定加載類來自哪個 jar 包。

使用 verbose:class 打印加載類

我們可以在 jvm 啟動腳本加入如下參數 -verbose:class,然后重啟,日志里會打印出每個類的加載信息。

java -verbose:class -classpath './*' xx.xx.Main

日志輸出如下:

通過這種方式可以看到加載類來源於哪個Jar包。

不過這種方式需要重啟應用,對生產系統來說,影響還是比較大,不太優雅。

Arthas 查到來源類

阿里開源項目 Arthas sc 命令可以用來查找加載類的信息。。

sc 命令是 Search-Class 簡寫,這個命令能搜索出已經加載到 JVM 中的 Class 信息,支持參數如下表格所示。

程序啟動之后,啟動 arthas,進入 A 應用。

運行如下命令:

sc -d org.example.App

輸出結果如下 :

code-source 顯示當前查找類 org.example.App 來自的 C。

另外我們可以 jad 命令反編譯類,在線查看源碼。

總結

這篇文章主要解釋應用中存在多個同名類,環境不同,類加載不同的原因。接着介紹了兩種快速查找運行應用依賴類來源的方法。

當定位到了沖突類的來源,我們可以顯示指定 classpath jar 包的順序,指定類加載的順序。但這只是暫時解決問題。本質上依賴沖突的問題,還是需要深層次排除的。

歡迎關注我的公眾號:程序通事,獲得日常干貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM