Java~类加载的基本过程-附带一道经典题目讲解

文章目录

      • 类加载的五个基本过程
      • 什么时候会触发类加载(重点)
      • 经典题目
      • 常见的一些类加载器
      • 什么是双亲委派模型

类加载的五个基本过程

  1. 加载 根据路径找到想对应的class文件 并将这个文件加载到Java虚拟机中(存储到方法区内)

类加载分为隐式类加载和显示类加载 隐式类加载是指在层程序使用new关键词创建的对象时, 会隐式的调用类的加载器把对应的类加载到JVM中. 显示类加载指的是通过直接调用class.forName() 方法把所需要的类加载到JVM中

  1. 检查: 检查类加载的class文件的正确性(文件格式检验 元数据验证 字节码验证 符号引用验证)
  2. 准备: 给类加载的静态变量分配内存空间并赋予初始默认值
  3. 解析: 虚拟机将常量池中的符号引用替换成直接引用得过程, 符号引用就可以理解为一个标识 而在直接引用就可以直接指向内存中的地址
  4. 初始化: 为静态变量和静态代码块指向初始化工作

什么时候会触发类加载(重点)

  1. 构造该类的实例的时候
  2. 调用该类的静态属性或方法的时候
  3. 使用子类时会触发父类的加载

经典题目

/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-08-04
 * Time: 15:34
 */

class One {
    public One() {
        System.out.println("One 构造");
    }

    {
        System.out.println("One 实例");
    }

    static {
        System.out.println("One 静态");
    }
}

class Two extends One {
    public Two() {
        System.out.println("Two 构造");
    }

    {
        System.out.println("Two 实例");
    }

    static {
        System.out.println("Two 静态");
    }
}

public class Demo1 extends Two{

    public static void main(String[] args) {
        System.out.println("开始");
        new Two();
        new Two();
        System.out.println("结束");
    }

}

结果为:

One 静态
Two 静态
开始
One 实例
One 构造
Two 实例
Two 构造
One 实例
One 构造
Two 实例
Two 构造
结束

  • 分析:
  1. 代码从main方法开始执行 main方法是Demo的静态方法 就会触发Demo的类加载
  2. Two是Demo的父类 所以在加载Demo前会先加载Two 但是呢one又是Two的父类 就又会优先加载
  3. 加载One的静态代码块输出 One静态
  4. 加载Two的静态代码块 输出 Two静态
  5. Demo没有静态代码块了就会执行main方法的内容 输出 开始
  6. 在构造Two的实例的时候会先构造One的实例 就会先执行One的代码块和构造方法 然后才执行Two的代码块和构造方法 所以输出One实例 One构造 Two实例 Two构造
  7. 进行第二次构造Two的时候和第一次构造一样输出One实例 One构造 Two实例 Two构造
  8. 最后打印结束
  • 如果我们稍微改一下代码让Demo不继承Two会发生怎样的情况
/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-08-04
 * Time: 15:34
 */

class One {
    public One() {
        System.out.println("One 构造");
    }

    {
        System.out.println("One 实例");
    }

    static {
        System.out.println("One 静态");
    }
}

class Two extends One {
    public Two() {
        System.out.println("Two 构造");
    }

    {
        System.out.println("Two 实例");
    }

    static {
        System.out.println("Two 静态");
    }
}

public class Demo1{

    public static void main(String[] args) {
        System.out.println("开始");
        new Two();
        new Two();
        System.out.println("结束");
    }

}

结果:

开始
One 静态
Two 静态
One 实例
One 构造
Two 实例
Two 构造
One 实例
One 构造
Two 实例
Two 构造
结束

  • 我们就会发现一开始打印的是 开始 其实我们只要抓住一点类加载就是在使用到他的时候才会发生加载 这个使用包括上面说到的三点 创建实例 访问静态属性或者方法 或者继承他的类要被使用

常见的一些类加载器

  1. BootstrapClassLoader 加载标准库中的类Sring ArrayList之类的
  2. ExternsionClassLoader 加载一个特殊目录中的类
  3. ApplicationClassLoader 加载应用程序自己写的类

什么是双亲委派模型

  • 本质描述的就是在类加载的时候根据类的.class文件查找的过程
  • 目的就是尽量让标准库的类优先被加载 例如你在代码中创建了一个java.long.String这样的类名 就和库中的类名冲突了 此时是优先加载标准库中的String
  • 具体的查找过程如下图所示 (前面说到的三个类加载器之间存在伪父子关系 意思就是没有继承关系 只是在加载器内部有一个parent属性 指向其父亲)
    Java~类加载的基本过程-附带一道经典题目讲解_第1张图片
  1. 举例给一个类名为java.long.string 让其加载器通过类名找到对应的class文件
  2. 先从AppClassLoader开始 但是他不会立刻开始查找 而是把类名交给他的父亲
  3. ExtClassLoader也不会立刻查找 而是再3交给他的父亲
  4. BootClassLoader拿到类名后只能亲自查找rt.jar 如果找到了 就直接加载 如果未找到就把类名交还给ExtClassLoader让其来查找
  5. ExtClassLoader在根据类名在ext目录中查找 如果找到了就加载未找到就把类名在还给AppClassLoader进行加载
  6. AppClassLoader再去CLASS_PATH环境变量-cp指定的目录中查找 找到就加载 未找到就抛出一个CLassNotFoundException
  • 是否能违背双亲委派模型呢?
    这是可以的 但是你破坏不了标准库中的三个类加载器的执行 至于你自己写的 你自己随便

你可能感兴趣的:(Java,jvm,java,面试)