这篇是类加载器相关的第三篇: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上能够),应该也是这个缘由。。。哎。。恼火