类的加载过程与类加载器重点
类的加载过程与类加载器重点
1 类加载机制
把描述类型的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。
2 类加载机制的特点
类的加载、连接和初始化过程都是在程序运行期间完成,缺点是会导致提前编译困难,也会增加类加载时的性能开销,优点是提供了极高的扩展性和灵活性。
3 类的生命周期
加载、验证、准备、解析、初始化、使用、卸载;验证、准备、解析统称为连接。
4 类加载阶段顺序
加载、验证、准备、初始化、卸载这五个阶段顺序一直,解析阶段不一定
5 类加载的时机
严格规定了有且只有六种情况必须立即对类进行初始化:
- 遇到 new、getstatic、putstatic 或 invokestatic 这四条字节码指时,如果类型没有进行过初始化,如
- 使用 new 关键字实例化对象
- 读取或设置一个类的静态字段
- 调用一个类的静态方法
- 反射调用的时候
- 父类没有初始化的时候
- 虚拟机启动时,指定一个要执行的主类
- jdk 7 新加入的动态语言支持,如果 MethodHandle 实例最后的解析结果为 REFgetStatic、REFputStatic、REFinvokeStatic、REFnewInvokeSpecial 四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化
- jdk 8 当一个接口中有被 default 关键字修饰的接口方法时,如果这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
这六种称为对一个类型进行主动引用,除此之外,所有引用类型的方式都不会触发初始化,称为被动引用
被动引用的例子:
- 通过子类引用父类的静态字段,不会导致子类初始化。对于静态字段,只有直接定义这个字段的类才会被初始化
- 通过数组定义来引用类,不会触发此类的初始化。
- 没有直接引用到定义常量的类,不会触发定义常量的类的初始化。因为常量在编译阶段会存入调用类的常量池中
6 接口与类加载的区别
类在初始化时,要求父类全部都已经初始化过,接口在初始化时,不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。
7 加载阶段的目的
将虚拟机外部的 Class 文件加载到虚拟机内存,存储在方法区之中
8 加载阶段的过程
- 获取: 通过一个类的全限定名来获取定义此类的二进制字节流
- 加载转化: 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 生成 Class 对象: 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口
数组类本身不通过类加载器创建,由 Java 虚拟机直接在内存中动态构造出来的,但数组的元素类型还是要靠类加载器来完成加载。
数组类的创建规则:
- 如果组件类型是引用类型,采用正常的类加载过程
- 如果组件类型不是引用类型,标记为与引导类加载器关联
- 数组类的可访问性和它的组件类型的可访问性一致
9 验证阶段的目的
确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
10 验证阶段的过程
文件格式验证、元数据验证、字节码验证、符号引用验证
- 文件格式验证: 验证字节流是否符合 Class 文件格式的规范,保证输入的字节流能正确地解析并存储于方法区之内
- 元数据验证: 对类的元数据信息进行语义校验
- 字节码验证: 对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为
- 符号引用验证: 对类引用的各类信息校验,确保解析行为能正常执行
11 准备阶段的目的
正式为类中的静态变量分配内存并设置初始值
12 准备阶段类变量的初始值
在准备阶段过后,类变量的初始值通常是数据类型的零值,赋值动作要到类的初始化阶段才会被执行,除非类字段是常量,在准备阶段就会直接赋值
13 解析阶段的目的
解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程
14 符号引用和直接引用的区别
符号引用:以一组符号来描述所引用的目标,只要使用时能定位到目标即可。与虚拟机的内存布局无关,不一定是已经加载到虚拟机内存当中的内容
直接引用:直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。与虚拟机的内存布局直接相关,必定已经在虚拟机的内存中存在。
15 解析阶段发生的时间
可以在类被加载器加载时就对常量池中的符号引用进行解析,也可以等到一个符号引用将要被使用前才去解析它。