深入Java虚拟机(九):类加载器和双亲委派模型

本文深入探讨Java类加载过程,解析双亲委派模型的工作原理,以及自定义类加载器的实现步骤。同时,详细介绍了Tomcat的类加载机制,包括Bootstrap、System、Common和Webapp类加载器的角色和加载顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类的唯一性

判断是否为同一个类的条件:

  • 类的完整类名必须一致,包括包名。
  • 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同。
//创建两个不同的自定义类加载器实例
FileClassLoader loader1 = new FileClassLoader(rootDir);
FileClassLoader loader2 = new FileClassLoader(rootDir);
//通过findClass创建类的Class对象
Class<?> object1=loader1.findClass("com.john.classloader.DemoObj");
Class<?> object2=loader2.findClass("com.john.classloader.DemoObj");

System.out.println("obj1:"+object1.hashCode());//obj1:723074861
System.out.println("obj2:"+object2.hashCode());//obj2:895328852

双亲委派模型

BootStrap ClassLoader称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,由C++实现,是虚拟机自身的一部分,没有父类。

EtxClassLoader称为扩展类加载器,负责加载Java的扩展类库,Java 虚拟机的实现会提供一个扩展库目录,该类加载器在此目录里面查找并加载 Java 类。默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。它由Java语言实现,父类加载器为null。

AppClassLoader称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。一般来说,Java 应用的类都是由它来完成加载的。它由Java语言实现,父类加载器为ExtClassLoader。

UserClassLoader称为自定义类加载器,父类加载器为AppClassLoader。

工作过程:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

优点

1、避免重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。

2、考虑到安全因素,java核心api中定义类型只会被启动类加载器加载,不会被随意替换。

自定义类加载器

ClassLoader类主要方法介绍:

1、loadClass(String)

该方法加载指定名称(包括包名)的二进制类型,该方法在JDK1.2之后不再建议用户重写但用户可以直接调用该方法,loadClass()方法是ClassLoader类自己实现的,该方法中的逻辑就是双亲委派模式的实现,其源码如下:

protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
  {
      synchronized (getClassLoadingLock(name)) {
          // 先从缓存查找该class对象,找到就不用重新加载
          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
                  // 如果都没有找到,则通过自定义实现的findClass去查找并加载
                  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;
      }
  }

当类加载请求到来时,先从缓存中查找该类对象,如果存在直接返回,如果不存在则交给该类加载去的父加载器去加载,倘若没有父加载则交给顶级启动类加载器去加载,最后倘若仍没有找到,则使用findClass()方法去加载。

2、findClass(String) 
在JDK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中,从前面的分析可知,findClass()方法是在loadClass()方法中被调用的,当loadClass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载,这样就可以保证自定义的类加载器也符合双亲委托模式。

3、defineClass(byte[] b, int off, int len) 
defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象。

自定义类加载器主要步骤:

1、继承ClassLoader类

2、重写findClass方法,之前已经提过在JDK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,但是在JDK1.2之后引入了双亲委派模型,如果再重写loadClass方法每次都要重写里面双亲委派的逻辑,所以建议重写findClass方法。

protected Class<?> findClass(String name) throws ClassNotFoundException {
      // 获取类的字节数组
      byte[] classData = getClassData(name);  
      if (classData == null) {
          throw new ClassNotFoundException();
      } else {
          //使用defineClass生成class对象
          return defineClass(name, classData, 0, classData.length);
      }
  }

tomcat类加载机制

如上图所示,tomcat在初始化时创建以下类加载器:

Bootstrap 类加载器

加载JVM启动所需的类以及标准扩展类($JAVA_HOME/jre/lib/ext下)

System类加载器

加载tomcat启动所需的类,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下。 
包含一下类:$CATALINA_HOME/bin/bootstrap.jar:tomcat server启动所需的main方法, 
$CATALINA_BASE/bin/tomcat-juli.jar:日志接口实现类,包含java.util.loggingAPI 。

Common 类加载器

这个类加载器包含对Tomcat内部类和所有Web应用程序都可见的附加类。 位于 $CATALINA_BASE/lib下

webappclassloader

每个web应用都有一个对应的类加载器实例,该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般加载器的顺序是相反的。这是java servlet规范中的推荐做法,其目的是是的web应用自己的类优先级高与web容器提供的类。这种代理模式的一个例外是:Java核心库是不再查找范围之内的。这也是为了保证Java核心库的类型安全。

因此,从web应用程序的角度来看,类或资源加载的顺序如下:

  • 使用Bootstrap 加载器加载
  • web application下的/WEB_INF/classes
  • web application下的/WEB_INF/lib/*.jar
  • System类加载器
  • Common类加载器

线程上下文类加载器Thread.getContextClassLoader()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值