了不起,我可能發現了Jar 包衝突的祕密

1、前言

這篇是類加載器相關的第三篇:html

實戰分析Tomcat的類加載器結構(使用Eclipse MAT驗證)java

仍是Tomcat,關於類加載器的趣味實驗linux

 

昨天下午剛寫了篇 類加載器相關的,晚上想着驗證個問題:Tomcat 跑了多個spring web項目,那麼org.springframework.web.servlet.DispatcherServlet 這種類是怎麼個狀況呢?多個不一樣類加載器加載的,同時存在的同名類?web

我是打算利用阿里開源的arthas工具來查看的,可是這個工具只支持 linux。說來也不怕讓人笑話,公司的後端服務,開發環境、測試環境用的windows的,之後交付給客戶不知道是用啥。先不說這個吧,反正咱們打的war包,在windows服務器的tomcat 上沒什麼問題。spring

可是當我把一樣的war包丟到 linux 上時,發現報錯了,沒啓動成功。。。。hahhah。。。尷尬。。。apache

錯誤以下:windows

Caused by: java.lang.NoSuchMethodError: javax.persistence.Table.indexes()[Ljavax/persistence/Index;
        at org.hibernate.cfg.annotations.EntityBinder.processComplementaryTableDefinitions(EntityBinder.java:936)
        at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:824)
        at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3790)
        at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3744)
        at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1410)
        at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1844)
        at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1928)
        at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:372)
        at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:454)
        at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:439)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
        ... 38 common frames omitted

 

大概意思是, javax.persistence.Table 的 indexes()方法不存在。後端

 

2、排查過程

首先,我在idea 中搜了一把 「javax.persistence.Table」,搜到的結果是,hibernate-jpa-2.1-api-1.0.0.Final.jar 這裏面有個同名的類,看了下,indexes()方法是存在的。api

好吧,學了一陣子類加載器了,我以爲,首先仍是看看,這個類是從哪加載的吧。 懶得去加 -XX:+TraceClassLoading參數了,直接 用阿里的神器,greys(用arthas也能夠,arthas是基於greys搞的) 掛載上去,用下面的命令搜索了一下。tomcat

ga?>sc -df javax.persistence.Table  
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                         class-info | javax.persistence.Table                                                          |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        code-source | /home/upload/apache-tomcat-8.5.28/webapps/CAD-WebService/WEB-INF/lib/persistence-api |
|                                                    | -1.0.jar                                                                         |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                               name | javax.persistence.Table                                                          |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        isInterface | true                                                                             |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                       isAnnotation | true                                                                             |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                             isEnum | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                   isAnonymousClass | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                            isArray | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                       isLocalClass | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                      isMemberClass | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        isPrimitive | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        isSynthetic | false                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        simple-name | Table                                                                            |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                           modifier | abstract,interface,public                                                        |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                         annotation | java.lang.annotation.Target,java.lang.annotation.Retention                       |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                         interfaces | java.lang.annotation.Annotation                                                  |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                        super-class |                                                                                  |
+----------------------------------------------------+----------------------------------------------------------------------------------+
|                                       class-loader | ParallelWebappClassLoader                                                        |

 

從上圖看出來,javax.persistence.Table 這個類啊,是 webappclassloader 從 webapps/CAD-WebService/WEB-INF/lib/persistence-api -1.0.jar 加載的。

因而我打開 這個jar包看了下,裏面確實有javax.persistence.Table ,這個類也確實沒有indexes()方法:

 

 看來問題就在這裏,是加載到了錯誤的jar包。 接下來的處理,就要結合業務代碼,看看究竟是從哪引入了這個包,這個包是否須要,不須要的話,直接排除掉便可。(可以使用idea 插件 maven helper)。

 若是隻是 儘快解決問題,通常到這步就能夠了。但我奇怪的是,windows上爲啥沒問題呢???(黑人問號)

 後邊在windows 的 tomcat 啓動腳本加了 -XX:+TraceClassLoading,發現,該類是從hibernate 那個jar包加載的,因此沒問題。(要讓windows上輸出類加載日誌,要修改點東西。https://www.cnblogs.com/welcomer/p/5068340.html)

 

3、根因分析

我看了下代碼,這個jar包,確實須要,不能排除掉。。。只是比較奇怪, 在linux上,爲啥會優先加載了 persitance-api.jar,難道在windows沒有先加載 persistence-api.jar?

帶着這些疑問,我惡向膽邊生,直接dump了windows下和linux的堆內存。

jmap -dump:live,format=b,file=heap3.bin 123072  -----linux的

jmap -dump:live,format=b,file=heap-windows.bin 11640 --windows的

 

eclipse mat 一把打開 linux的堆dump後,用 oql 語句,查詢了一下全部的 ParallelWebappClassLoader:

 

 

好,再看看 windows 的,操做和上面差很少,直接看結果:

 

 

上圖可見,windows上,是按字母序來的, hibernate那個包,妥妥地排在 persistence-api.jar 的前面。。。 這讓人不得不吐槽下,這個順序怎麼搞的,linux上文件感受跟亂序同樣。。。

 

因爲 tomcat 8 纔有localRepositories 這個字段,我這裏沒有可運行的源碼,因此只能大概看看 spring-boot 內嵌的tomcat jar包的源碼了,大概是這麼個方法:

org.apache.catalina.loader.WebappClassLoaderBase#start

 public void start() throws LifecycleException {

        state = LifecycleState.STARTING_PREP;

        WebResource classes = resources.getResource("/WEB-INF/classes");
        if (classes.isDirectory() && classes.canRead()) {
            localRepositories.add(classes.getURL());
        }
 WebResource[] jars = resources.listResources("/WEB-INF/lib");
        for (WebResource jar : jars) {
            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
 localRepositories.add(jar.getURL());                 jarModificationTimes.put(
                        jar.getName(), Long.valueOf(jar.getLastModified()));
            }
        }

        state = LifecycleState.STARTED;
    }

 

上面標紅處,就是 去 /WEB-INF/lib 下面獲取全部的 jar 包,而後遍歷,加入到localRepositories。 這裏看來,去讀文件系統後,沒有根據文件名排序吧。。。而正好呢,windows下和linux 下返回的文件列表,順序不一樣。

 

4、總結

綜上,能夠大概總結下,通常來講,不一樣操做系統返回的文件,順序都是不太一致的,若是代碼裏,直接依賴了這種順序,就會出現這類:

測試:小哥哥,你程序有bug。。。

你:不可能,我這好好的。。。

測試:小哥哥,不騙你,你過來看嘛。。。

你:不看不看,煩不煩??

 

想到之前遇到的一個 spring 循環依賴的問題(linux上不行,windows上能夠),應該也是這個緣由。。。哎。。惱火

相關文章
相關標籤/搜索