上篇文章中,小黑哥分析 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