1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        從Jar包沖突搞到類加載機制,就是這么霸氣

        共 5263字,需瀏覽 11分鐘

         ·

        2021-10-09 01:17

        接手了一套比較有年代感的系統(tǒng),計劃把重構(gòu)及遇到的問題寫成系列文章,老樹發(fā)新枝,重溫一些實戰(zhàn)技術(shù),分享給大家?!局貥?gòu)01篇】,給大家講講Jar包沖突及原理。

        背景

        目前市面上項目管理要么是基于Maven,要么是基于Gradle,最近接手了一套純手動添加jar包的項目。

        對于純手動添加jar包的項目已經(jīng)是多年前的方式了,現(xiàn)在工作三五年的技術(shù)人員可能都沒有經(jīng)歷過。就是把項目中所需的jar包挨個找出來,添加到一個lib目錄中,在IDE中再將jar包依賴手動添加上。

        這種方式來添加jar包依賴,不僅費事,而且很容易出現(xiàn)jar包沖突,同時分析沖突手段,只能憑借經(jīng)驗。

        最近就遇到這樣一種情況:一個項目在開發(fā)者A的環(huán)境中可以正常啟動,在B那里就無法啟動,而異常信息是找不到什么什么類。

        稍微有一些開發(fā)經(jīng)驗的人,馬上就可以斷定是jar包沖突導(dǎo)致。下面就看看如何解決及引申出來的知識點。

        臨時解決方案

        由于暫時無法對項目進(jìn)行大范圍重構(gòu),也不敢輕易將Jar包進(jìn)行替換升級。只能采用臨時的手段來進(jìn)行解決。

        這里總結(jié)幾個步驟以備不時之需,通常也是解決Jar依賴問題的小技巧。

        第一:在IDE中查找異常中找不到的類。比如IDEA MAC操作系統(tǒng),我用的快捷鍵是command + shift + n。

        查找沖突

        以Assert類為例,可以看到有很多包都包含了Assert,但啟動程序卻報找不到該類的某個方法,問題基本上就出在Jar包沖突上了。

        第二,定位到Jar包沖突之后,找到系統(tǒng)本應(yīng)該使用的Jar包。

        比如這里需要使用的spring-core中的類,而不spring.jar中的類。那么,就可以利用JVM的類加載順序機制,讓JVM先加載spring-core的jar包。

        知識點:在同一目錄下的jar包,JVM是按照jar包的先后順序進(jìn)行加載,一旦一個全路徑名相同的類被加載之后,后面再有相同的類便不會進(jìn)行加載了。

        因此,臨時解決方案就是調(diào)整JVM編譯(加載)Jar包的順序。這個在Eclipse和Idea中都有支持,可以手動進(jìn)行調(diào)整。

        Eclipse中調(diào)整方式:

        Eclipse調(diào)整順序

        Idea中調(diào)整方式:

        Idea調(diào)整順序

        把需要優(yōu)先加載的jar包往上調(diào)整,這樣就可以優(yōu)先加載它,總算是臨時解決了jar包沖突的問題。

        類加載機制的延伸

        上面只是受限于項目現(xiàn)狀的臨時解決方案,最終肯定是要進(jìn)行改造升級的,基于Maven或Gradle進(jìn)行Jar包管理,同時解決掉Jar包沖突的問題的。

        在這個臨時解決方案,涉及到一個JVM的關(guān)鍵知識點:JVM的類加載器的隔離問題及雙親委派機制。如果沒有JVM類加載機制的相關(guān)知識,可能連上面的臨時方案都無法想到。

        類加載器的隔離問題

        每個類裝載器都有一個自己的命名空間用來保存已裝載的類。當(dāng)一個類裝載器裝載一個類時,它會通過保存在命名空間里的類全局限定名(Fully Qualified Class Name) 進(jìn)行搜索來檢測這個類是否已經(jīng)被加載了。

        JVM 對類唯一的識別是 ClassLoader id + PackageName + ClassName,所以一個運行程序中是有可能存在兩個包名類名完全一致的類的。并且如果這兩個類不是由一個 ClassLoader 加載,是無法將一個類的實例強轉(zhuǎn)為另外一個類的,這就是 ClassLoader 隔離性。

        為了解決類加載器的隔離問題,JVM引入了雙親委派機制。

        雙親委派機制

        雙親委派機制的核心有兩點:第一,自底向上檢查類是否已加載;其二,自頂向下嘗試加載類

        類加載器

        類加載器通常有四類:啟動類加載器、拓展類加載器、應(yīng)用程序類加載器和自定義類加載器。

        暫且不考慮自定義類加載器,JDK自帶類加載器具體執(zhí)行過程如下:

        第一:當(dāng)AppClassLoader加載一個class時,會把類加載請求委派父類加載器ExtClassLoader去完成;

        第二:當(dāng)ExtClassLoader加載一個class時,會把類加載請求委派BootStrapClassLoader去完成;

        第三:如果BootStrapClassLoader加載失敗(例如在%JAVA_HOME%/jre/lib里未查找到該class),會使用ExtClassLoader來嘗試加載;

        第四:如果ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,如果AppClassLoader也加載失敗,則會報出異常ClassNotFoundException

        ClassLoader的雙親委派實現(xiàn)

        ClassLoader通過loadClass()方法實現(xiàn)了雙親委托機制,用于類的動態(tài)加載。

        該方法的源碼如下:

        protected?Class?loadClass(String?name,?boolean?resolve)
        ????????throws?ClassNotFoundException{
        ????????synchronized?(getClassLoadingLock(name))?{
        ????????????//?First,?check?if?the?class?has?already?been?loaded
        ????????????Class?c?=?findLoadedClass(name);
        ????????????if?(c?==?null)?{
        ????????????????long?t0?=?System.nanoTime();
        ????????????????try?{
        ????????????????????if?(parent?!=?null)?{
        ????????????????????????c?=?parent.loadClass(name,?false);
        ????????????????????}?else?{
        ????????????????????????c?=?findBootstrapClassOrNull(name);
        ????????????????????}
        ????????????????}?catch?(ClassNotFoundException?e)?{
        ????????????????????//?ClassNotFoundException?thrown?if?class?not?found
        ????????????????????//?from?the?non-null?parent?class?loader
        ????????????????}

        ????????????????if?(c?==?null)?{
        ????????????????????//?If?still?not?found,?then?invoke?findClass?in?order
        ????????????????????//?to?find?the?class.
        ????????????????????long?t1?=?System.nanoTime();
        ????????????????????c?=?findClass(name);

        ????????????????????//?this?is?the?defining?class?loader;?record?the?stats
        ????????????????????sun.misc.PerfCounter.getParentDelegationTime().addTime(t1?-?t0);
        ????????????????????sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
        ????????????????????sun.misc.PerfCounter.getFindClasses().increment();
        ????????????????}
        ????????????}
        ????????????if?(resolve)?{
        ????????????????resolveClass(c);
        ????????????}
        ????????????return?c;
        ????????}
        ????}

        loadClass方法本身是一個遞歸向上調(diào)用的過程,上述代碼中從parent.loadClass的調(diào)用就可以看出。

        在執(zhí)行其他操作之前,首先通過findLoadedClass方法從最底端的類加載器開始檢查是否已經(jīng)加載指定的類。如果已經(jīng)加載,則根據(jù)resolve參數(shù)決定是否要執(zhí)行連接過程,并返回Class對象。

        而Jar包沖突往往發(fā)生在這里,當(dāng)?shù)谝粋€同名的類被加載之后,在這一步檢查時就會直接返回,不會再加載真正需要的類。那么,程序用到該類時就會拋出找不到類,或找不到類方法的異常。

        Jar包的加載順序

        上面已經(jīng)看到一旦一個類被加載之后,全局限定名相同的類可能就無法被加載了。而Jar包被加載的順序直接決定了類加載的順序。

        決定Jar包加載順序通常有以下因素:

        • 第一,Jar包所處的加載路徑。也就是加載該Jar包的類加載器在JVM類加載器樹結(jié)構(gòu)中所處層級。上面講到的四類類加載器加載的Jar包的路徑是有不同的優(yōu)先級的。
        • 第二,文件系統(tǒng)的文件加載順序。因Tomcat、Resin等容器的ClassLoader獲取加載路徑下的文件列表時是不排序的,這就依賴于底層文件系統(tǒng)返回的順序,當(dāng)不同環(huán)境之間的文件系統(tǒng)不一致時,就會出現(xiàn)有的環(huán)境沒問題,有的環(huán)境出現(xiàn)沖突。

        本人遇到的問題屬于第二種因素中的一個分支情況,即同一目錄下不同Jar包的加載順序不同。因此,通過調(diào)整Jar包的加載順序就暫時解決了問題。

        Jar包沖突的通常表現(xiàn)

        Jar包沖突往往是很詭異的事情,也很難排查,但也會有一些共性的表現(xiàn)。

        • 拋出java.lang.ClassNotFoundException:典型異常,主要是依賴中沒有該類。導(dǎo)致原因有兩方面:第一,的確沒有引入該類;第二,由于Jar包沖突,Maven仲裁機制選擇了錯誤的版本,導(dǎo)致加載的Jar包中沒有該類。
        • 拋出java.lang.NoSuchMethodError:找不到特定的方法。Jar包沖突,導(dǎo)致選擇了錯誤的依賴版本,該依賴版本中的類對不存在該方法,或該方法已經(jīng)被升級。
        • 拋出java.lang.NoClassDefFoundError,java.lang.LinkageError等,原因同上。
        • 沒有異常但預(yù)期結(jié)果不同:加載了錯誤的版本,不同的版本底層實現(xiàn)不同,導(dǎo)致預(yù)期結(jié)果不一致。

        Tomcat啟動時Jar包和類的加載順序

        最后,梳理一下Tomcat啟動時,對Jar包和類的加載順序,其中包含上面提到的不同種類的類加載器默認(rèn)加載的目錄:

        • $java_home/lib 目錄下的java核心api;
        • $java_home/lib/ext 目錄下的java擴展jar包;
        • java -classpath/-Djava.class.path所指的目錄下的類與jar包;
        • $CATALINA_HOME/common目錄下按照文件夾的順序從上往下依次加載;
        • $CATALINA_HOME/server目錄下按照文件夾的順序從上往下依次加載;
        • $CATALINA_BASE/shared目錄下按照文件夾的順序從上往下依次加載;
        • 項目路徑/WEB-INF/classes下的class文件;
        • 項目路徑/WEB-INF/lib下的jar文件;

        上述目錄中,同一文件夾下的Jar包,按照順序從上到下一次加載。如果一個class文件已經(jīng)被加載到JVM中,后面相同的class文件就不會被加載了。

        小結(jié)

        Jar包沖突在我們的日常開發(fā)中是非常常見的問題,如果能夠很好理解沖突的原因及底層機制,可以極大的提高解決問題的能力和團(tuán)隊影響力。因此,在不少面試中都會被提及此類問題。

        這篇文章我們重點講了手動添加依賴情況下導(dǎo)致Jar包沖突的原因及解決方案。在解決該問題時往往還會設(shè)計到Maven對Jar包沖突管理的一些策略,比如依賴傳遞原則、最短路徑優(yōu)先原則、最先聲明原則等,我們下篇文章再來詳細(xì)聊聊。


        往期推薦

        如何輕松給Spring Boot配置文件加個密?

        08篇 要給Nacos的UDP通信功能點個贊

        程序員天天寫代碼,如何拓展社交圈?

        05 網(wǎng)絡(luò)面經(jīng):使用HTTPS就絕對安全了嗎?

        04 網(wǎng)絡(luò)面經(jīng):HTTP 2.0的這些新特性,是時候了解一下了



        如果你覺得這篇文章不錯,那么,下篇通常會更好。添加微信好友,可備注“加群”(微信號:zhuan2quan)。

        一篇文章就看透技術(shù)本質(zhì)的人,
        ? 和花一輩子都看不清的人,
        ? 注定是截然不同的搬磚生涯。
        ▲?按關(guān)注”程序新視界“,洞察技術(shù)內(nèi)幕
        瀏覽 90
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            女按摩技师把我弄硬了口述 | 日本少妇的性生活 | 大尺度做爰无遮挡床戏 | 亚洲无码性爱视频 | 女同桌用胸把我精子弄出来了 | 国语少妇高潮在线观看 | 亚洲第二页 | 理论片琪琪午夜电影 | 成人视频免费观看视频网址 | 日本护士洗澡xxxxx |