這篇是類加載器相關的第三篇: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()方法不存在。後端
首先,我在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)
我看了下代碼,這個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上能夠),應該也是這個緣由。。。哎。。惱火