首页 > 编程知识 正文

类加载机制作用,同时加载不同版本的jar包

时间:2023-05-04 11:58:23 阅读:32570 作者:7

计划接手有年代感的系统,把重构和遇到的问题写成一系列文章,老树长出新枝,重温实战技术,分享给大家。 【重构01篇】说明Jar包的碰撞和原理。

目前,市面上的项目管理基于Maven或Gradle,最近接手了手动添加jar包的项目。

手动添加jar包的项目已经是多年前的方式了,现在工作三五年的技术人员可能没有经历过。 在项目中逐个找到所需的jar包,将其添加到lib目录中,然后在IDE中手动添加jar包。

以这种方式添加jar包依赖关系不仅麻烦,而且容易发生jar包冲突。 另外,分析冲突手段,只能依靠经验。

最近,遇到了一个项目在开发人员a的环境中正常启动,在b中无法启动,但什么异常信息都找不到的情况。

如果是有开发经验的人,很快就可以判断是因为jar包的冲突。 接下来,我们来看看如何解决和挖掘出的知识点。

临时解决方案是无法轻松替换和升级Jar包,因为无法大范围重构项目。 只能用临时手段解决。

这里根据需要总结几个步骤。 它通常也是解决Jar依赖问题的小技巧。

第一,在IDE中查找在异常情况下找不到的类。 例如,对于IDEA MAC,我使用的快捷键是command shift n。

以Assert类为例,很多包都包含Assert,但启动程序报告找不到该类所在的方法,问题基本上在于Jar包的冲突。

第二,在确定Jar包冲突后,系统将找到应该使用的Jar包。

例如,必须使用spring-core类,而不是spring.jar类。 在中,可以使用JVM的类加载顺序机制,让JVM先加载spring-core的jar包。

知识点:在同一目录下的jar包,JVM是按照jar包的先后顺序进行加载,一旦一个全路径名相同的类被加载之后,后面再有相同的类便不会进行加载了。

因此,临时解决方案是调整JVM编译(加载) Jar包的顺序。 Eclipse和Idea都支持这一点,可以手动调整。

Eclipse中的调整方法:

Idea中的调整方法:

将需要优先加载的jar包向上调整,以便可以优先加载,总算暂时解决了jar包冲突的问题。

类加载机制扩展是局限于项目现状的临时解决方案,最终必须进行改造升级,基于Maven或Gradle进行Jar包管理,同时解决Jar包冲突问题。

此临时解决方案涉及JVM的类加载器隔离问题和父母委托机制,这是JVM的关键知识点。 如果您不了解JVM类的加载机制,您可能甚至无法考虑上述临时方案。

每个类加载器隔离问题类装载器都有自己的命名空间用于存储加载的类。 类加载器加载类时,它会在命名空间中存储的http://www.Sina.com/(fullyqualifiedclassname )中进行搜索,以检测是否加载了类。

由于JVM对类的唯一标识是classloaderidpackagenameclassname,因此一个可执行文件具有完全匹配3358www.Sina.com/和类全局限定名的类2 此外,如果两个类不是由一个类加载器加载的,则无法将一个类的实例强烈转换为另一个类。 这就是类加载器的分离性。

为了解决类加载器的包名,JVM引入了类名

父母委任机制父母委任机制的核心有两点:第一,隔离问题检验类为双亲委派机制其二,3358 www.Sina.com/http://www

通常,类加载器分为四类:启动类加载器、扩展类加载器、APP应用程序类加载器和自定义类加载器。

JDK附带了类加载器,而不考虑自定义类加载器。 具体步骤如下。

第一,AppClassLoader加载类时,去http://www.Sina.com/ext class loader完成类的加载请求;

第二,ExtClassLoader加载类时,去BootStrapClassLoader完成类加载请求自底向上

第三,如果BootStrapClassLoader加载失败,例如,在%JAVA_HOME%/jre/lib中找不到class,则尝试使用ExtClassLoader加载;

第四,如果ExtClassLoader也加载失败,则使用AppClassLoader加载;如果AppClassLoader也加载失败,则报告异常的ClassNo

tFoundException。

ClassLoader的双亲委派实现

ClassLoader通过loadClass()方法实现了双亲委托机制,用于类的动态加载

该方法的源码如下:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }

loadClass方法本身是一个递归向上调用的过程,上述代码中从parent.loadClass的调用就可以看出。

在执行其他操作之前,首先通过findLoadedClass方法从最底端的类加载器开始检查是否已经加载指定的类。如果已经加载,则根据resolve参数决定是否要执行连接过程,并返回Class对象。

而Jar包冲突往往发生在这里,当第一个同名的类被加载之后,在这一步检查时就会直接返回,不会再加载真正需要的类。那么,程序用到该类时就会抛出找不到类,或找不到类方法的异常。

Jar包的加载顺序

上面已经看到一旦一个类被加载之后,全局限定名相同的类可能就无法被加载了。而Jar包被加载的顺序直接决定了类加载的顺序。

决定Jar包加载顺序通常有以下因素:

第一,Jar包所处的加载路径。也就是加载该Jar包的类加载器在JVM类加载器树结构中所处层级。上面讲到的四类类加载器加载的Jar包的路径是有不同的优先级的。第二,文件系统的文件加载顺序。因Tomcat、Resin等容器的ClassLoader获取加载路径下的文件列表时是不排序的,这就依赖于底层文件系统返回的顺序,当不同环境之间的文件系统不一致时,就会出现有的环境没问题,有的环境出现冲突。

本人遇到的问题属于第二种因素中的一个分支情况,即同一目录下不同Jar包的加载顺序不同。因此,通过调整Jar包的加载顺序就暂时解决了问题。

Jar包冲突的通常表现

Jar包冲突往往是很诡异的事情,也很难排查,但也会有一些共性的表现。

抛出java.lang.ClassNotFoundException:典型异常,主要是依赖中没有该类。导致原因有两方面:第一,的确没有引入该类;第二,由于Jar包冲突,Maven仲裁机制选择了错误的版本,导致加载的Jar包中没有该类。抛出java.lang.NoSuchMethodError:找不到特定的方法。Jar包冲突,导致选择了错误的依赖版本,该依赖版本中的类对不存在该方法,或该方法已经被升级。抛出java.lang.NoClassDefFoundError,java.lang.LinkageError等,原因同上。没有异常但预期结果不同:加载了错误的版本,不同的版本底层实现不同,导致预期结果不一致。 Tomcat启动时Jar包和类的加载顺序

最后,梳理一下Tomcat启动时,对Jar包和类的加载顺序,其中包含上面提到的不同种类的类加载器默认加载的目录:

$java_home/lib 目录下的java核心api;$java_home/lib/ext 目录下的java扩展jar包;java -classpath/-Djava.class.path所指的目录下的类与jar包;$CATALINA_HOME/common目录下按照文件夹的顺序从上往下依次加载;$CATALINA_HOME/server目录下按照文件夹的顺序从上往下依次加载;$CATALINA_BASE/shared目录下按照文件夹的顺序从上往下依次加载;项目路径/WEB-INF/classes下的class文件;项目路径/WEB-INF/lib下的jar文件;

上述目录中,同一文件夹下的Jar包,按照顺序从上到下一次加载。如果一个class文件已经被加载到JVM中,后面相同的class文件就不会被加载了。

小结

Jar包冲突在我们的日常开发中是非常常见的问题,如果能够很好理解冲突的原因及底层机制,可以极大的提高解决问题的能力和团队影响力。因此,在不少面试中都会被提及此类问题。

这篇文章我们重点讲了手动添加依赖情况下导致Jar包冲突的原因及解决方案。在解决该问题时往往还会设计到Maven对Jar包冲突管理的一些策略,比如依赖传递原则、最短路径优先原则、最先声明原则等,我们下篇文章再来详细聊聊。

博主简介:《SpringBoot技术内幕》技术图书作者,酷爱钻研技术,写技术干货文章。

公众号:「程序新视界」,博主的公众号,欢迎关注~

技术交流:请联系博主微信号:zhuan2quan


“ 程序新视界”,一个100%技术干货的公众号

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。