上篇文章中,小黑哥分析 Maven 依賴衝突分爲兩類:java
第二種狀況,每每是這個場景,本地/測試環境運行的都是好好的,上線以後測試就是不行。shell
這其實與 JVM
類加載有關,本地/測試環境加載正確類,而生產環節加載錯的類,爲何會這樣?segmentfault
主要有兩個緣由:緩存
JVM
類加載具備緩存機制,每一個類加載的時候首先檢查一遍,類是否被當前類加載器加載。若未被加載,先交給其父類加載器加載,父類加載器不能加載,纔會交給當前類加載器。網絡
當前類加載器加載完成以後,將會將其緩存起來。jvm
類加載的核心源碼位於 ClassLoader#loadClass
:ide
① 處將會檢查ClassLoader#findLoadedClass
最終將會調用 ClassLoader#findLoadedClass0
,這是一個 native
方法,最終將會根據類名加類加載器爲鍵值查找緩存。測試
每一個類加載器負責的加載範圍都不同:idea
BootstrapClassLoader
引導類加載加載最核心的類庫,如 $JAVA_HOME/jre/lib/
ExtClassLoader
擴展類加載器負責加載$JAVA_HOME/jre/lib/ext
下的一些擴展類AppClassLoader
應用類加載器將加載 classpath
指定的類。咱們運行的應用依賴的各類類,通常將會由 AppClassLoader
記載,同名類被加載後,下次碰到就不會再被加載。spa
畫外音:利用緩存加快查詢速度
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
包。
咱們能夠在 jvm
啓動腳本加入以下參數 -verbose:class
,而後重啓,日誌裏會打印出每一個類的加載信息。
java -verbose:class -classpath './*' xx.xx.Main
日誌輸出以下:
經過這種方式能夠看到加載類來源於哪一個Jar
包。
不過這種方式須要重啓應用,對生產系統來講,影響仍是比較大,不太優雅。
阿里開源項目 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