一、思维导图
01类加载机制.png二、大纲
2.1 类的加载是什么?
-
将类(.class)文件中的二进制数据读入内存,并放在运行时数据库的方法区内,在堆区创建java.lang.Class对象(封装类在方法区的数据结构)。
-
类加载最终产品是堆区的Class对象(封装了类在方法区的数据结构,并向Java程序员提供了访问方法区内的数据结构的接口)。
2.2 类的生命周期(重要)
2.2.1 类的生命周期图
类的生命周期.png注意:顺序执行的意思是开始,而不是完成后进行下一步
2.2.2 过程
1. 加载
-
查找并加载类的二进制数据,在Java堆中创建java.lang.Class对象
JVM需要做的三件事
- 通过类的全限定名获取其定义的二进制字节流
- 将此字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在Java堆中创建java.lang.Class对象,作为访问方法区数据的入口
-
获取class文件的5种方式
- 本地系统中直接加载
- 网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 专有数据库提取.class文件
- Java源文件动态编译为.class文件
注意:此阶段可控性最强,可用系统自带类加载器也可自定义类加载器
2. 连接
2.1 验证
四个阶段
-
文件格式
如0xCAFEBABE开头等 -
元数据
语义分析,如是否有父类(不含Object) -
字节码
语义合法 -
符号引用验证
保证解析能正确执行
注意:重要但不必须,如所引用类经反复验证,可采用-Xverifynone关闭
2.2 准备
为类的静态变量分配内存,并将其初始化默认值
注意:
- 仅包括类变量(static),不含实例变量(在实例化时随对象一块分配在堆中)
- 此初始值指数据类型默认的零值,非显示赋予的值
- 若类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,在准备阶段变量会被初始化为ConstantValue属性做指定的值
2.3 解析
把类中的符号引用(一组符号来描述)转化为直接引用(直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄)
直接指针访问对象.png
句柄访问对象.png
-
符号引用
- 接口
- 字段
- 类方法
- 接口方法
- 方法类型
- 方法句柄
- 调用点限制符
-
加载顺序不确定
某些情况下在初始化之后,为了支持Java语言的运行时绑定(动态绑定或晚期绑定)
3. 初始化
为类的静态变量赋予正确的初始值
设定的两种方式
- 声明类变量是指定初始值
- 使用静态代码块为类变量指定初始值
JVM初始化步骤
- 若此类未被加载和连接,则程序先加载并连接该类
- 若此类的直接父类未初始化,则先初始化其直接父类
- 若类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机
只有当对类的主动使用的时候才会导致类的初始化
- 六种主动使用
- 创建类的实例,即new
- 访问某个类或接口的静态变量,或对该静态变量赋值
- 调用类的静态方法
- 反射(如Class.forName("com.insigim.Test"))
- 初始化某个类的子类,则父类也被初始化
- Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类
4. 使用
- new出对象程序中使用
5. 卸载
- 执行垃圾回收
2.2.3 几种情况下会结束生命周期
- 执行System.exit()方法
- 程序正常执行结束
- 程序执行过程中遇到了异常或错误而异常终止
- 由于操作系统出现错误而导致Java虚拟机进程终止
2.3 类加载器
1. 几种加载器的层次关系
类加载器层次关系.png2. 两种角度分类
2.1 JVM角度
启动类加载器
- 使用C++实现(仅限Hotspot)
- 虚拟机自身一部分
所有其他类加载器
- Java语言实现
- 独立虚拟机之外
- 继承抽象类java.lang.ClassLoader
- 需由启动类加载器加载到内存中才能加载其他的类
2.2 开发人员角度
启动类加载器
- Bootstrap ClassLoader,C++实现
- 负责加载存放在JDK\jre\lib下,或被-Xbootclasspath参数指定的路径中,且能被JVM识别的类库(如rt.jar,所有java.开头的类)
- 无法被Java程序直接引用
扩展类加载器
- Extension ClassLoader,由sun.misc.launcher$ExtClassLoader实现
- 负责加载JDK\jre\lib\ext目录中,或java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类)
- 开发者可直接使用扩展类加载器
应用程序类加载器
- Application ClassLoader,由sun.misc.Launcher$AppClassLoader实现
- 负载加载用户类路径(ClassPath)所指定的类
- 开发者可直接使用该类加载器,若应用程序未自定义自己的类加载器,此即为默认的类加载器
3. JVM类加载机制(重要)
-
全盘负责
当一个类加载器加载类时,此类所依赖和引用的其它Class都由此加载器负责,除非显示使用另一个类加载器 -
父类委托
先让父类加载器试图加载,只有父类无法加载,才尝试从自己的类路径加载该类 -
缓存机制
保证所有加载过的Class都被缓存,程序需使用时优先从缓存中加载,没有先找二进制数据在堆中创建Class对象,加入缓存,所以每次Class修改需重启JVM,修改才生效
2.4 类的加载
有三种方式
- 命令行启动应用时候由JVM初始化加载
- 通过Class.forName()方法动态加载
- 通过ClassLoader.loadClass()方法动态加载
Class.forName()与ClassLoader.loadClass()加载的区别?
- Class.forName():将类的.class文件加载到JVM中之外,还会对类进行解释,执行类中的static块
- ClassLoader.loadClass():只干一件事,就是将.class文件加载到JVM中,不执行statiic中内容(newInstance才会执行)
- Class.forName(name,initialize,loader)带参函数也可控制是否加载static块,并且只有调用newInstance()方法采用调用构造函数,创建类的对象
2.5 双亲委派模型(重要)
工作流程
若类加载器收到类加载的请求,首先把请求委托父加载器去完成,依次向上,因此所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围内没有找到所需的类时(无法加载),子加载器才尝试自己去加载该类
双亲委派机制
- 当AppClassLoader加载一个class时,先委派ExtClassLoader加载
- 当ExtClassLoader加载一个class时,先委派BootStrapClassLoader去加载
- 若BootStrapClassLoader加载失败(JDK\jre\lib无法找到该类),使用ExtClassLoader加载
- ExtClassLoader也加载失败(JDK\jre\lib\ext无法找到该类),则由AppClassLoader加载,若还失败则报出异常ClassNotFoundException
源码分析
public Class<?> loadClass(String name)throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
// 首先判断该类型是否已经被加载
Class c = findLoadedClass(name);
if (c == null) {
//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
try {
if (parent != null) {
//如果存在父类加载器,就委派给父类加载器加载
c = parent.loadClass(name, false);
} else {
//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,
//通过调用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
意义
- 系统类防止内存中出现多份同样的字节码
- 保证Java程序安全稳定运行
JDK1.9后的委派模型
JDK9后的类加载器委派关系.png2.6 自定义类加载器
一般情况,由三种类加载器互相配合进行加载,如有必要,也可加入自定义类加载器
- 如应用是通过网络来传输Java类的字节码,为保证安全性,这些字节码经过加密处理,系统类加载器无法加载,则需要自定义加载器实现
- 一般继承loadClasser类,只需重写findClass方法即可
- 核心在于对字节码文件的获取,如果加密的字节码则需要在该类中对文件进行解密
- 传递的文件名是全限定性名称
- 最好不要重写loadClass方法,容易破坏双亲委托模式
- 不要把类放在AppClassLoader路径下,容易导致AppClassLoader类加载器加载
因JVM自带ClassLoader只懂从本地文件加载标准java cass文件,若编写自己的ClassLoader,需做到以下三点
- 执行非置信代码之前,自动验证数字签名
- 动态地创建符合用户特定需要的定制化构建类
- 从特定的场所所取得java class,如从数据库中和网络中
网友评论