Java中类加载器

文章目录

    • 前言
    • 1. 常用的类加载器
    • 2. 双亲委派模型
      • 2.1. 双亲委派模型介绍
      • 2.2. 双亲委派模型实现源码分析
      • 2.3. 双亲委派模型的好处
      • 2.4. 如果我们不想使用双亲委派模型怎么办?
    • 3. 自定义类加载器
    • 4. 总结

前言

类加载过程:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析

数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。

所有的类都由类加载器加载,加载的作用就是将 .class文件加载到内存。

类加载过程我们不在这里详细说了,有需要的小伙伴可以去看下这篇文章哦:《Java中类的一生是如何度过的?》

1. 常用的类加载器

JVM 中内置了三个重要的类加载器(ClassLoader),除了 BootStrapClassLoader 其他类加载器均由 Java 实现且全部集成自 java.lang.ClassLoader

  1. BootStrapClassLoader(启动类加载器): 最顶层的加载类,由 C++ 实现,负责加载 %JAVA_HOME%/lib 目录下的 jar 包和类,或者被 -Xbootclasspath 参数指定的路径中的所有的类。
  2. ExtensionClassLoader(扩展类加载器): 主要负责加载目录 %JRE_HOME%/lib/ext% 目录下的 jar 包和类,或被 java.ext.dirs 系统变量所指定的路径下的 jar 包。
  3. AppClassLoader(应用程序类加载器): 面向我们用户的加载器,负责加载当前应用 classpath 下的所有的 jar 包和类。

2. 双亲委派模型

2.1. 双亲委派模型介绍

每一个类都有一个对应它的类加载器。 系统中的 ClassLoader 在协同工作的时候回默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派父类加载器的 loadClass() 方法处理,因为所有的请求最终都应该传送到顶层的启动类加载器 BootStrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父亲加载器为 null 时 ,会使用启动类加载器 BootStrapClassLoader 作为父类加载器。

Java中类加载器_第1张图片

每个类都有一个父类加载器,我们通过下面的程序来验证。

public class Test {

    public static void main(String[] args) {
        System.out.println("Test 的类加载器是:"+ Test.class.getClassLoader());
        System.out.println("Test 的父类的类加载器是:"+Test.class.getClassLoader().getParent());
        System.out.println("Test 的父类的的父亲的类加载器是:"+Test.class.getClassLoader().getParent().getParent());
    }

}

输出为:

Test 的类加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2
Test 的父类的类加载器是:sun.misc.Launcher$ExtClassLoader@694f9431
Test 的父类的的父亲的类加载器是:null

从上面程序可以看出:

ApplicaionClassLoader 的父类加载器是 ExtClassLoaderExtClassLoader 的父类加载器为 nullnull 并不代表 ExtClassLoader 没有父类加载器,而是 BootStrapClassLoader

其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个 Mother ClassLoader 和一个 Father ClassLoader 。另外,类加载器之间的“父子”关系也不是通过继承来体现的,是由“优先级”来决定。

2.2. 双亲委派模型实现源码分析

双亲委派模型的实现代码非常简单,逻辑也非常的清晰,都集中在 java.lang.ClassLoaderloadClass() 中:

private final ClassLoader parent; 
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
    // 首先,检查请求的类是否已经被加载过
    Class<?> c = findLoadedClass(name);
    if (c == null) {
      long t0 = System.nanoTime();
      try {
        if (parent != null) {//父加载器不为空,调用父加载器loadClass()方法处理
          c = parent.loadClass(name, false);
        } else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
          c = findBootstrapClassOrNull(name);
        }
      } catch (ClassNotFoundException e) {
        //抛出异常说明父类加载器无法完成加载请求
      }

      if (c == null) {
        long t1 = System.nanoTime();
        //自己尝试加载
        c = findClass(name);

        // 这是定义类加载器;记录统计数据。
        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
        sun.misc.PerfCounter.getFindClasses().increment();
      }
    }
    if (resolve) {
      resolveClass(c);
    }
    return c;
  }
}

2.3. 双亲委派模型的好处

双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载( JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有双亲委派模型,而是每个类加载器加载自己的话就会出现一个问题,比如我们编写一个称 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

2.4. 如果我们不想使用双亲委派模型怎么办?

自定义加载器的话,需要继承 ClassLoader 。如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法

3. 自定义类加载器

除了 BootStrapClassLoader ,其他的类加载器均由 Java 实现且继承自 java.lang.ClassLoader 。如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader

4. 总结

本篇文章讲解了 Java中类加载器,代码和笔记由于纯手打,难免会有纰漏,如果发现错误的地方,请第一时间告诉我,这将是我进步的一个很重要的环节。以后会定期更新算法题目以及各种开发知识点,如果您觉得写得不错,不妨点个关注,谢谢。

你可能感兴趣的:(面试题,java,jvm,类加载)