相关文章推荐
完美的苦瓜  ·  Fragment可见性总结 | ...·  4 月前    · 
温柔的沙滩裤  ·  ArcGIS Javascript API ...·  1 年前    · 
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包冲突管理的一些策略,比如依赖传递原则、最短路径优先原则、最先声明原则等,我们下篇文章再来详细聊聊。



从Jar包冲突搞到类加载机制,就是这么霸气插图4

关注公众号: 程序新视界 ,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为 程序新视界 原创文章,转载必须以链接形式标明本文链接

本文链接: https://www.choupangxia.com/2021/10/05/jar-jvm/