文章目录
一、内存结构概述
二、类加载器和类的加载过程
2.1 类加载子系统作用
2.2 类加载器ClassLoader角色
2.3 类的加载过程
2.3.1 加载
2.3.2 链接(即验证、准备、解析)
2.3.3 初始化
三、类加载器分类
3.1 自定义类与核心类库的加载器
3.2 虚拟机自带的加载器
3.3 用户自定义加载器
四、ClassLoader的常用方法及获取方法
4.1 ClassLoader继承关系
4.2 代码示例
五、双亲委派机制
5.1 双亲委派机制工作原理
5.2 代码示例
5.3 双亲委派机制的优势
5.4 双亲委派机制在SPI中的应用
5.5 沙箱安全机制
六、其他
6.1 JVM中表示两个class对象是否为同一个类
6.2 对类加载器的引用
6.3 类的主动使用和被动使用
一、内存结构概述
如果自己想手写一个Java虚拟机的话,主要考虑哪些结构呢?
- 类加载器
- 执行引擎
复杂版的详细图
本文针对Class Loader SubSystem这一块展开讲解类加载子系统的工作流程。
二、类加载器和类的加载过程
2.1 类加载子系统作用
类加载子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识即16进制CA FE BA BE;
加载后的Class类信息存放于一块成为 方法区 的内存空间。除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定
如果调用构造器实例化对象,则其实例存放在堆区
2.2 类加载器ClassLoader角色
- class file 存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候要加载到JVM当中来根据这个文件实例化出n个一模一样的实例。
- class file 加载到JVM中,被称为DNA元数据模板,放在方法区。
- 在.class文件 —> JVM —> 最终成为元数据模板,此过程就要一个运输工具(类装载器 Class Loader),扮演一个快递员的角色。
2.3 类的加载过程
看代码
public class HelloLoader { public static void main(String[] args) { System.out.println("谢谢ClassLoader加载我...."); System.out.println("你的大恩大德,我下辈子再报!"); } }
它的加载过程是怎么样的呢?
执行 main() 方法(静态方法)就需要先加载承载类 HelloLoader
加载成功,则进行链接、初始化等操作,完成后调用 HelloLoader 类中的静态方法 main
加载失败则抛出异常
完整的流程图如下所示:加载 --> 链接(验证 --> 准备 --> 解析) --> 初始化
2.3.1 加载
通过一个类的全限定名获取定义此类的二进制字节流;
将这个字节流所代表的的静态存储结构转化为方法区的运行时数据;
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
加载class文件的方式
从本地系统中直接加载
通过网络获取,典型场景:Web Applet
从zip压缩包中读取,成为日后jar、war格式的基础
运行时计算生成,使用最多的是:动态代理技术
由其他文件生成,典型场景:JSP应用从专有数据库中提取.class文件,比较少见
从加密文件中获取,典型的防Class文件被反编译的保护措施
2.3.2 链接(即验证、准备、解析)
验证
目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
准备
为类变量分配内存并且设置该类变量的默认初始值,即零值;
这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;
之类不会为实例变量分配初始化,类变量会分配在方法去中,而实例变量是会随着对象一起分配到java堆中。
解析
将常量池内的符号引用转换为直接引用的过程。
事实上,解析操作网晚会伴随着jvm在执行完初始化之后再执行
符号引用就是一组符号来描述所引用的目标。符号应用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info/CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
2.3.3 初始化
- clinit()即“class or interface initialization method”,注意他并不是指构造器init(),初始化阶段就是执行类构造器方法()的过程
- 此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
- 我们注意到如果没有静态变量c,那么字节码文件中就不会有clinit方法
构造器方法clinit()中指令按语句在源文件中出现的顺序执行
虚拟机必须保证一个类的clinit()方法在多线程下被同步加锁。
即一个类只需被clinit一次,之后该类的内部信息就被存储在方法区。
可以看到线程2并不会重复执行初始化操作。