2虚拟机如何加载类
三大步骤:加载、链接、初始化
基本数据类型由Java虚拟机原先定义好,引用数据类型,泛型会被擦除,数组由虚拟机直接生成,类和接口对应字节流。
加载
定义:查找字节流并创建类结构
数据源:jar文件、class文件、网络数据源等
数组没有字节流由Java虚拟机直接生成,其他类需借助类加载器完成查找字节流过程。
类加载器
功能
功能:加载类,提供命名空间
类的唯一性是由类加载器实例以及类的全名确定。
同一串字节流由不同的类加载器加载,会得到两个不同的类。可借此,来运行同一个类的不同版本。
分类
- 启动类加载器boot class loader
最为基础、最为重要的类,如JRE lib目录下jar包中的类以及Xbootclasspath制定的类 - 扩展类加载器 extension class loader
boot class loader的子类
负责相对次要又通用类的加载,如JRE lib/ext目录下jar包中的类以及由系统变量java.ext.dirs制定的类
Java 9 改名为平台类加载器 platform class loader - 应用加载器 application class loader
extension class loader的子类
加载应用程序路径下的类,(应用程序路径指虚拟机参数 -cp/-classpath、系统变量 java.class.path 或环境变量 CLASSPATH 所指定的路径)
默认应用程序中包含的类由应用类加载器加载 - 自定义类加载器
如实现class文件加密,加载时解密
双亲委派模型
类加载器接收到加载请求时,先将请求转发给父类加载器,附加在其没有找到该类时,它再尝试加载。避免重复加载。
链接
三个阶段:验证、准备、解析
- 验证
确保加载类满足JVM约束 - 准备
- 为加载类的静态字段分配内存,静态字段初始化在初始化阶段进行
- 构造跟其他和类层次相关的数据结构,如动态绑定方法表
- 解析
将常量池中的符号引用替换为直接引语
.class文件加载到JVM前,类不知道其他类及其他类的方法、字段对应的具体地址,若引用其他类成员时,JAVA编译器会生成一个符号引用指代目标方法类名、方法名、参数类型、返回值类型
解析目的就是将指代符号解析为实际引用,若引用的类未加载则触发加载此类(但未必链接及初始化它)
JVM规范要求在执行前完成引用符号的解析,没有要求连接过程中完成解析
初始化
为常量值的字段赋值,执行clinit方法
clinit方法:Java编译器会把所有final静态字段赋值和静态代码块中的代码置于clinit方法中
初始化触发时机
- 虚拟机启动时,初始化用户指定的主类;
- 初始化 new 指令的目标类;
- 调用静态方法的指令时,初始化该静态方法所在的类;
- 访问静态字段的指令时,初始化该静态字段所在的类;
- 子类的初始化会触发父类的初始化;
- 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
- 使用反射API对某个类进行反射调用时,初始化这个类;
- 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。
总结
加载是指查找字节流,并且据此创建类的过程。加载需要借助类加载器,在Java虚拟机中,类加载器使用了双亲委派模型,即接收到加载请求时,会先将请求转发给父类加载器。
链接,是指将创建成的类合并至Java虚拟机中,使之能够执行的过程。链接还分验证、准备和解析三个阶段。其中,解析阶段为非必须的。
初始化,则是为标记为常量值的字段赋值,以及执行 < clinit > 方法的过程。类的初始化仅会被执行一次,这个特性被用来实现单例的延迟初始化。
问题
1 | public static Object getInstance(boolean flag) { |
会加载,不会链接、初始化。flag=true时链接、初始化。