了不得,我可能發現了Jar 包沖突的秘密


一、前言

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

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

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

 

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

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

但是當我把同樣的war包丟到 linux 上時,發現報錯了,沒啟動成功。。。。hahhah。。。尷尬。。。

錯誤如下:

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()方法不存在。

 

二、排查過程

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

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

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)

 

三、根因分析

我看了下代碼,這個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 下返回的文件列表,順序不同。

 

四、總結

綜上,可以大概總結下,一般來說,不同操作系統返回的文件,順序都是不太一致的,如果代碼里,直接依賴了這種順序,就會出現這類:

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

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

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

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

 

想到以前遇到的一個 spring 循環依賴的問題(linux上不行,windows上可以),應該也是這個原因。。。哎。。惱火

 


免責聲明!

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



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