推荐链接:
总结——》【Java】
总结——》【Mysql】
总结——》【Redis】
总结——》【Kafka】
总结——》【Spring】
总结——》【SpringBoot】
总结——》【MyBatis、MyBatis-Plus】
总结——》【Linux】
总结——》【MongoDB】
总结——》【Elasticsearch】
Java——》类加载机制(过程)
所谓类加载机制就是虚拟机把Class文件加载到内存,并对数据进行校验,转换解析和初始化,形成可以虚拟机直接使用的Java类型,即java.lang.Class
阶段 | 过程 | 功能 |
---|---|---|
装载 | 装载 | 查找和导入class文件 |
链接 | 验证 | 保证被加载类的正确性 |
准备 | 为类的静态变量分配内存,并将其初始化为默认值 | |
解析 | 把类中的符号引用转换为直接引用 | |
初始化 | 初始化 | 初始化类的静态变量 初始化类的静态代码块 初始化类的父类 |
一、装载
1、装载的具体步骤
- 通过一个类的全限定名,获取定义此类的二进制字节流
- 将这个字节流所代码的静态存储结构,转化为方法区的运行时数据结构
- 在Java堆中生成一个代码这个类的java.lang.Class对象,作为对方法区这些数据的访问入口
2、获取类的二进制字节码方式
方式 | 描述 |
---|---|
从本地获取 | 通过一个类的全限定名,从本地文件系统加载类的字节码文件 |
从网络中获取 | 使用类加载器的getResourceAsStream()方法,通过指定URL的方式来获取InputStream,然后通过读取InputStream获取字节码数据。 |
运行时计算生成 | 作用动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流 |
3、如何实现通过类全名来获取此类的二进制字节流
“类加载器”实现通过类全名来获取此类的二进制字节流,并且在java虚拟机外部去实现,以便让应用程序决定如何获取所需要的类。
二、链接
1、验证:保证被加载类的正确性
Q:验证哪些内容?
A:1)文件格式验证
2)元数据验证
3)字节码验证
4)符号引用验证
(1)文件格式验证
验证的内容:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
验证的目的:保证输入的字节流能正确地解析并存储于方法区之内。
验证的方式:基于二进制字节流进行的,只有经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面验证都是基于方法区的存储结构进行的。
示例:
- 是否以16进制cafebaby开头
- 版本号是否正确
(2)元数据验证
验证的内容:对类的元数据信息进行语义校验(Java语法校验)
验证的目的:保证不存在不符合Java语法规范的元数据信息。
示例:
- 是否有父类
- 是否继承了final类:final类是不能被继承的,继承了就会出现问题。
- 一个非抽象类是否实现了所有的抽象方法
(3)字节码验证
验证的内容:进行数据流和控制流分析,确定程序语义是合法的、符合逻辑的。对类的方法体进行校验分析,保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。
示例:
- 运行检查
- 栈数据类型和操作码操作参数吻合(比如栈空间只有4个字节,但是我们实际需要的远远大于4个字节,那么这个时候这个字节码就是有问题的)
- 跳转指令指向合理的位置
(4)符号引用验证
验证的内容:对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。
验证的目的:确保解析动作能正常执行。
示例:
- 常量池中描述类是否存在
- 访问的方法或者字段是否存在且具有足够的权限
2、准备:静态变量分配内存,初始化为默认值
(1)静态变量有默认值
public class Test {
private static int i;
public static void main(String[] args) {
// 正常打印出0,因为静态变量i在准备阶段会有默认值0
System.out.println(i);
}
}
(2)局部变量没有赋值不能被使用
public class Test {
public static void main(String[] args) {
// 编译通不过,因为局部变量没有赋值不能被使用
int i;
System.out.println(i);
}
}
3、解析:把类中的符号引用转换为直接引用
(1)符号引用 VS 直接引用?
符号引用:就是一组符号来描述目标,可以是任何字面量。
直接引用:就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
符号引用:手机号
直接引用:手机号的主人
(2)解析主要是针对哪些符号引用?
1)类或接口
2)字段
3)类方法
4)接口方法
5)方法类型
6)方法句柄
7)调用限定符
三、初始化:初始化类的静态变量、静态代码块、父类
初始化阶段是执行类构造器方法的过程。
1、在Java中对类变量进行初始值设定
- 声明类变量时指定初始值
- 使用静态代码块为类变量指定初始值
2、初始化步骤
- 假如这个类还没有被加载和连接,则程序先加载并链接该类
- 假如该类的直接父类还没有被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
四、使用
1、主动引用
只有当对类的主动使用的时候才会导致类的初始化,类的主动使用有六种:
- 创建类的实例,也就是new的方式
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(如 Class.forName(“com.carl.Test”) )
- 初始化某个类的子类,则其父类也会被初始化
- Java虚拟机启动时被标明为启动类的类(JvmCaseApplication ),直接使用 java.exe 命令来运行某个主类
2、被动引用
- 引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
- 定义类数组,不会引起类的初始化。
- 引用类的static final常量,不会引起类的初始化(如果只有static修饰,还是会引起该类初始化的)。
五、卸载
在类使用完之后,如果满足下面的情况,类就会被卸载:
- 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。但是一般情况下启动类加载器加载的类不会被卸载,而我们的其他两种基础类型的类加载器只有在极少数情况下才会被卸载。