什么是类加载
类加载:就是将类的class文件读入内存,并创建一个Java.lang.class对象。类在运行期第一次使用时,首先会被类加载器动态加载至JVM。类的加载过程又分为了五个阶段:加载、验证、准备、解析、初始化。为了方便记忆可以编为一个口诀:家(加载)宴(验证)准备(准备)了西(解析)式(初始化)菜。
类的生命周期被分为了七个阶段:加载、验证、准备、解析、初始化、使用、限载
接下来我们就具体介绍一下类加载的过程
加载
“加载”是类加载的第一个阶段,在加载过程中主要完成了以下三件事:
- 通过反射获取定义该类的二进制字节流
- 将该字节流静态储存结构转换为原空间运行时储存结构
- 在内存中生成一个Class对象代表元空间该类各类数据的访问入口
验证
该步骤是验证类或接口的Class文件中的中的二进制,确保二进制中的信息符合虚拟机规范要求,并且不会危害到虚拟机的自身安全。
准备
准备阶段是为类变量分配元空间内存并设置默认初始值,类变量指的是static修饰的变量。注:如果类变量是常量,那么不会定义默认初始值,而是直接初始化为表达式定义的值。
例:
public static int value=100;//初始值为0
public static final int value=100;//初始值为100
解析
解析阶段是将常量池的符号引用替换为直接引用的过程。为了支持Java的动态绑定解析的过程在有些情况下可以在初始化阶段之后执行。
初始化
初始化是类加载的最后一步,在初始化阶段开始执行Java代码。初始化阶段是虚拟机执行类构造器<clinit>()的阶段,在这个阶段要对类变量再一次赋表达式定义的值。
类加载的时机
在介绍完类加载的过程之后,我们对类加载的过程有了一个初步的了解,那么类究竟是什么时候加载的呢?要触发什么样的机制才会加载类呢?
主动引用
虚拟机规定在五种情况下必须进行类加载:
- 当出现new、getstatic、putstatic、invokestatic这些指令时会进行类加载
new指令:程序创建一个类的实例对象时
getstatic指令:当程序访问类的静态变量时
putstatic指令:当程序给静态变量赋值时
invokestatic指令:当程序调用静态方法时
注:程序访问类的静态常量不会触发类加载机制
- 当通过反射机制的Class.forName("...")或newInstance()等方法时触发类加载
- 当虚拟机启动时,该类中包含main()方法时触发类加载
- 在JDK8中,当接口中的实习类调用了接口的默认方法,如果这个实现类要发生类加载首先会触发接口的类加载
除了以上五种触发类加载的引用类其他引用类都不会触发类加载,被称为被动引用
被动引用
常见被动引用
- 当子类引用父类的静态字段时,不会触发子类的类加载机制
- 当通过数组来定义引用类时,不会触发该类的类加载机制
- 引用常量不会造成定义常量的类加载,因为常量储存在常量池,本质上没有直接引用到定义常量的类
类加载器分类
类加载器分为:启动类加载器、扩展类加载器。应用程序类加载器
双亲委派模型
应用程序是由通常是由三种类加载器互相配合,从而实现类加载,除此之外还可以加入自己定义的类加载器。类加载器之间的层次关系,被称为双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。父子关系是组合关系而不是继承关系。
工作机制:一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。
作用:
- 使得基础类得到统一,避免冲突
- 实现热加载,比如Spring Boot DevTools