一、前言

这篇是类加载器相关的第三篇:

实战分析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上可以),应该也是这个原因。。。哎。。恼火

了不得,我可能发现了Jar 包冲突的秘密的更多相关文章

  1. 曹工杂谈:一例简单的Jar包冲突解决示例

    Jar包冲突的相关文章: 了不得,我可能发现了Jar 包冲突的秘密   一.前言 jar包冲突分多种,简单理解来说,就是同package且同名的类在多个jar包内出现,如果两个jar包在同一个clas ...

  2. cxf spring restful 问题解决(jar包冲突)

    SEVERE: Context initialization failedorg.springframework.beans.factory.BeanCreationException: Error ...

  3. 万恶的jar包冲突

    搭了个spring+struts2+mybatis的项目架子, 好久不用myEclipse和tomcat了,生疏了好多. 建议还是去百度一些框架整合的博客,直接使用博客里面给的jar包列表里的jar包 ...

  4. weblogic与axis2 jar包冲突

    1.org.springframework.web.util.NestedServletException: Handler processing failed; nested exception i ...

  5. 记一次jar包冲突

    题记:永远不要在同一个项目中,引用不同版本的两个jar包,否则,这可能就是一个大坑. 在做网校项目的时候,帮助中心要使用lucene,所以就引入了lucene-5.5.1的包,删掉了原先存在于项目中的 ...

  6. 重新看待Jar包冲突问题及解决方案

    Jar包冲突是老生常谈的问题,几乎每一个Java程序猿都不可避免地遇到过,并且也都能想到通常的原因一般是同一个Jar包由于maven传递依赖等原因被引进了多个不同的版本而导致,可采用依赖排除.依赖管理 ...

  7. 【原】Maven解决Jar包冲突

    一.起源 引入二方jar maven 包后出现 NoSuchMethodError org.apache.commons.lang3.StringUtils.isNoneEmpty . 第一感觉就是j ...

  8. 三十、详述使用 IntelliJ IDEA 解决 jar 包冲突的问题

    在实际的 Maven 项目开发中,由于项目引入的依赖过多,遇到 jar 冲突算是一个很常见的问题了.在本文中,我们就一起来看看,如何使用 IntelliJ IDEA 解决 jar 包冲突的问题!简单粗 ...

  9. jar包冲突问题

    这两天在启动一个新项目的时候,项目一直启动不了,报StackOverFlow; java.util.concurrent.ExecutionException: java.lang.StackOver ...

随机推荐

  1. 使用Cordova将您的前端JavaScript应用打包成手机原生应用

    假设我用JavaScript和HTML开发了一个前端应用,我想把该应用打包成能直接在手机上安装和运行(不通过浏览器)的原生应用,例如像下面这样.对应用的用户来说,他们得到的用户体验和真正的用Andro ...

  2. Heacher互助平台 α版本冲刺

    课程属性 作业课程 https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/ 作业链接 https://edu.cnblogs.co ...

  3. 新数据的GT列表

    制作新数据集时需要重新制作train_GT,test_GT 代码: dic = {} with open('/home/bnrc/all_image_GT.txt','r') as file: for ...

  4. Hermite 矩阵及其特征刻画

    将学习到什么 矩阵 \(A\) 与 \(\dfrac{1}{2}(A+A^T)\) 两者生成相同的二次型,而后面那个矩阵是对称的,这样以来,为了研究实的或者复的二次型,就只需要研究由对称矩阵生成的二次 ...

  5. OS X快捷键小技巧

    退出command+Q,关分页Command+W,刷新Command+R,新开分页Command+T 全屏 ctrl+command+F 每个Mac使用者都知道点击下窗口左上角黄色圆形的按钮就可以最小 ...

  6. js中声明函数的三种方式

    己亥年  庚午月 癸巳日  宜入宅 忌婚嫁 函数声明方式 声明 : function first(){}: 调用:first() 函数表达式声明方式   声明: var second=function ...

  7. PAT (Basic Level) Practise (中文)-1030. 完美数列(25)

    PAT (Basic Level) Practise (中文)-1030. 完美数列(25)   http://www.patest.cn/contests/pat-b-practise/1030 给 ...

  8. 关于Java IO流学习总结

    一.IO流的三种分类方式 1.按流的方向分为:输入流和输出流 2.按流的数据单位不同分为:字节流和字符流 3.按流的功能不同分为:节点流和处理流     二.IO流的四大抽象类: 字符流:Reader ...

  9. sessionStorage 的数据会在同一网站的多个标签页之间共享吗?这取决于标签页如何打开

    一直以来,我所以为的 sessionStorage 的生命周期是这样的:在 sessionStorage 中存储的数据会在当前浏览器的同一网站的多个标签页中共享,并在此网站的最后一个标签页被关闭后清除 ...

  10. 【Java_Spring】控制反转IOC(Inversion of Control)

    1. IOC的概念 控制反转IoC(Inversion of Control)是一种设计思想,而DI(依赖注入)是实现IoC的一种方法.在没有使用IOC的程序中,对象间的依赖关系是靠硬编码的方式实现的 ...