JVM 的运行机制学习
Java基础JVM之本地方法接口/本地方法库程序计数器<六>
JVM学习之类加载器学习
1.类加载器是什么
JVM启动的时候创建出运行时内存的空间,
类加载器是将.class文件生成Class对象,并存储到运行时内存的方法区中,为执行引擎提供类的信息
- 第一步:Loading加载
通过类的全限定名(包名 + 类名),获取到该类的
.class文件的二进制字节流将二进制字节流所代表的静态存储结构,转化为方法区运行时的数据结构
在
内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
总结:加载二进制数据到内存 —> 映射成jvm能识别的结构 —> 在内存中生成class文件。
- 第二步:Linking链接
链接是指将上面创建好的class类合并至Java虚拟机中,使之能够执行的过程,可分为验证、准备、解析三个阶段。
① 验证(Verify)
确保class文件中的字节流包含的信息,符合当前虚拟机的要求,保证这个被加载的class类的正确性,不会危害到虚拟机的安全。
② 准备(Prepare)
为类中的
静态字段分配内存,并设置默认的初始值,比如int类型初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了
③ 解析(Resolve)
解析阶段的目的,是将常量池内的符号引用转换为直接引用的过程(将常量池内的符号引用解析成为实际引用)。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。)
事实上,解析器操作往往会伴随着 JVM 在执行完初始化之后再执行。 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《Java 虚拟机规范》的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
解析动作主要针对类、接口、字段、类方法、接口方法、方法类型等。对应常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
- 第三步:initialization初始化
初始化就是执行类的构造器方法
init()的过程。这个方法不需要定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并来的。
若该类具有父类,
jvm会保证父类的init先执行,然后在执行子类的init。
简单理解:
类加载器就像一个搬运工,将磁盘的.class文件搬运到内存中,并告诉JVM这些类能用了
-
加载类:将类字节码读取到 JVM 内存
-
连接类:验证、准备、解析,保证类结构正确且可被引用
-
初始化类:执行静态变量赋值和静态代码块
-
支持动态加载:允许在运行时加载类,实现插件化或热更新
2.类加载分类
| 类加载器 | 实现方式 | 加载范围 | 说明 |
|---|---|---|---|
| Bootstrap(启动类加载器) | C/C++ | 核心类库 | 加载 rt.jar 或模块化 java.base,JVM 核心类 |
| Extension(扩展类加载器) | Java | 扩展库 | 加载 jre/lib/ext/ 下的类库或 java.ext.dirs 指定路径 |
| Application(应用类加载器) | Java | 应用类 | 加载应用的类和依赖的第三方库 |
| 自定义类加载器 | Java | 特定需求 | 热部署、加密字节码、模块化插件等 |
双亲委派模型(爹来模式)
当一个类加载器需要加载某个类时,它不会首先自己尝试加载,而是将这个任务委派给它的父类加载器去完成。只有当父类加载器无法加载这个类时,子类加载器才会尝试自己去加载
- JVM 默认采用双亲委派机制:类加载请求先委托给父类加载器,父类加载失败才自己加载
- 目的是保证核心类库不会被篡改,同时避免重复加载同一类
public class ClassLoaderDemo {
public static void main(String[] args) {
// 查看当前类的类加载器
ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("当前类的类加载器: " + classLoader);
// 查看父类加载器
ClassLoader parentClassLoader = classLoader.getParent();
System.out.println("父类加载器: " + parentClassLoader);
// 查看顶层类加载器(启动类加载器,通常显示为null)
ClassLoader bootstrapClassLoader = parentClassLoader.getParent();
System.out.println("启动类加载器: " + bootstrapClassLoader);
// 查看Java核心类的加载器
ClassLoader stringClassLoader = String.class.getClassLoader();
System.out.println("String类的加载器: " + stringClassLoader);
}
}
3.类加载器的重要特性
- 委派性:遵循双亲委派模型
- 可见性:子类加载器可见父类加载器加载的类,反之不成立
- 唯一性:同一类由不同类加载器加载会被视为不同的类
总结
类加载器是 JVM 的关键组件,负责将磁盘上的.class 字节码文件加载到内存,转换为 Class 对象并存储在方法区,为执行引擎提供类信息,通过双亲委派,保证类加载的唯一