jvm类加载器详解

类加载器是什么

类加载器简言之,就是用于把 .class文件中的字节码信息转化为具体 的java.lang.Class对象的过程的工具。

具体过程:

  1. 在实际类加载过程中, JVM会将所有的 .class字节码文件中的二进制数据读入内存中,导入运行时数据区的方法区中。
  2. 当一个类首次被主动加载被动加载时,类加载器会对此类执行类加载的流程 – 加载连接验证准备解析)、初始化
  3. 如果类加载成功,堆内存中会产生一个新的 Class对象, Class对象封装了类在方法区内的数据结构

类加载的过程

类加载的过程分为三个步骤(五个阶段) :加载 -> 连接验证准备解析)-> 初始化

加载验证准备初始化这四个阶段发生的顺序是确定的,而解析阶段可以在初始化阶段之后发生,也称为动态绑定晚期绑定

加载

加载:查找并加载类的二进制数据的过程。

加载的过程描述:

  1. 通过类的全限定名定位 .class文件,并获取其二进制字节流
  2. 把字节流所代表的静态存储结构转换为方法区运行时数据结构
  3. Java中生成一个此类的 java.lang.Class对象,作为方法区中这些数据的访问入口

连接

连接:包括验证准备解析三步。

验证

验证:确保被加载的类的正确性。验证是连接阶段的第一步,用于确保 Class字节流中的信息是否符合虚拟机的要求。

准备

准备:为类的静态变量分配内存,并将其初始化为默认值。准备过程通常分配一个结构用来存储类信息,这个结构中包含了类中定义的成员变量方法接口信息等。

具体行为:

  • 这时候进行内存分配的仅包括类变量( static),而不包括实例变量实例变量会在对象实例化时随着对象一块分配在 Java中。
  • 这里所设置的初始值通常情况下是数据类型默认的零值(如 00Lnullfalse等),而不是被在 Java代码中被显式赋值

解析

解析:把类中对常量池内的符号引用转换为直接引用

解析动作主要针对类或接口字段类方法接口方法方法类型方法句柄调用点限定符等7类符号引用进行。

初始化

初始化:对类静态变量赋予正确的初始值 (注意和连接时的解析过程区分开)。

初始化的目标

  • 实现对声明类静态变量时指定的初始值的初始化;
  • 实现对使用静态代码块设置的初始值的初始化。

初始化的步骤

  1. 如果此类没被加载连接,则先加载连接此类;
  2. 如果此类的直接父类还未被初始化,则先初始化其直接父类;
  3. 如果类中有初始化语句,则按照顺序依次执行初始化语句。

初始化的时机

  • 创建类的实例( new关键字);
  • java.lang.reflect包中的方法(如: Class.forName(“xxx”));
  • 对类的静态变量进行访问或赋值;
  • 访问调用类的静态方法
  • 初始化一个类的子类父类本身也会被初始化;
  • 作为程序的启动入口,包含 main方法(如: SpringBoot入口类)。

类的主动引用和被动引用

主动引用

主动引用:在类加载阶段,只执行加载连接操作,不执行初始化操作。

主动引用的几种形式

  • 创建类的实例( new关键字);
  • java.lang.reflect包中的方法(如: Class.forName(“xxx”));
  • 对类的静态变量进行访问或赋值;
  • 访问调用类的静态方法
  • 初始化一个类的子类父类本身也会被初始化;
  • 作为程序的启动入口,包含 main方法(如: SpringBoot入口类)。

被动引用

被动引用: 在类加载阶段,会执行加载连接初始化操作。

被动引用的几种形式:

  1. 通过子类引用父类的的静态字段,不会导致子类初始化;
  2. 定义类的数组引用不赋值,不会触发此类的初始化;
  3. 访问类定义的常量,不会触发此类的初始化。

三种类加载器

类加载器:类加载器负责加载程序中的类型(类和接口),并赋予唯一的名字予以标识。

  • BootstrapClassloader 是在 Java虚拟机启动后初始化的。
  • BootstrapClassloader 负责加载 ExtClassLoader,并且将 ExtClassLoader的父加载器设置为 BootstrapClassloader。
  • BootstrapClassloader 加载完 ExtClassLoader 后,就会加载 AppClassLoader,并且将 AppClassLoader 的父加载器指定为 ExtClassLoader

双亲委托机制

核心思想:其一,自底向上检查类是否已加载;其二,自顶向下尝试加载类

具体加载过程

  1. AppClassLoader加载一个 class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派父类加载器 ExtClassLoader去完成。
  2. ExtClassLoader加载一个 class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派BootStrapClassLoader去完成。
  3. 如果 BootStrapClassLoader加载失败(例如在 %JAVA_HOME%/jre/lib里未查找到该 class),会使用 ExtClassLoader来尝试加载;
  4. 如果 ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,如果 AppClassLoader也加载失败,则会报出异常 ClassNotFoundException

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

类的动态加载

类的几种加载方式

  • 通过命令行启动时由 JVM初始化加载;
  • 通过 Class.forName()方法动态加载;
  • 通过 ClassLoader.loadClass()方法动态加载。

Class.forName()和ClassLoader.loadClass()

  • Class.forName():把类的 .class文件加载到 JVM中,对类进行解释的同时执行类中的 static静态代码块
  • ClassLoader.loadClass():只是把.class文件加载到 JVM中,不会执行 static代码块中的内容,只有在 newInstance才会去执行。

对象的初始化

静态变量/静态代码块 -> 普通代码块 -> 构造函数

  1. 父类静态变量静态代码块(先声明的先执行);
  2. 子类静态变量静态代码块(先声明的先执行);
  3. 父类普通成员变量普通代码块(先声明的先执行);
  4. 父类的构造函数
  5. 子类普通成员变量普通代码块(先声明的先执行);
  6. 子类的构造函数

自定义类加载器

Step 1:定义待加载的目标类 Parent.javaChildren.java

Step 2:实现自定义类加载器 CustomClassLoader extends ClassLoader