Java 虚拟机中的类加载即从 class 文件到内存中的类, 按先后顺序需要经过加载, 链接以及初始化三大步骤.
虚拟机的加载对象是什么?
上文中说过 Java 中有两种类型: 基本类型和引用类型, 而基本类型是由虚拟机预先定义好的, 引用类型中的泛型参数又会在编译过程中被擦除, 所以加载的对象就剩下类, 接口和数组类.
在类, 接口和数组类中, 数组类是由 Java 虚拟机直接生成的, 其他两种则有对应的字节流. 无论是直接生成的数组类, 还是加载的类, Java 虚拟机都需要对其进行链接和初始化. 接下来, 就详细介绍一下每个步骤具体都在干些什么.
虚拟机的加载流程是什么?
1. 加载
是指查找字节流, 并且据此创建类的过程. 上面提过数组类是由 Java 虚拟机直接生成的, 所以加载过程针对的是生成字节流的类与接口. 如何找到这些字节流, 则需要虚拟机借助类加载器.
启动类加载器是由 C++ 实现的, 没有对应的 Java 对象, 因此在 Java 中只能用 null 来指代. 在 Java 9 之前, 启动类加载器负责加载最为基础, 最为重要的类, 比如存放在 JRE 的 lib 目录下 jar 包中的类 (以及由虚拟机参数 -Xbootclasspath 指定的类). 除了启动类加载器之外, 另外两个重要的类加载器是扩展类加载器(extension class loader) 和应用类加载器(application class loader), 均由 Java 核心类库提供. 故除了启动类加载器之外, 其他的类加载器都是 java.lang.ClassLoader 的子类, 因此有对应的 Java 对象.
扩展类加载器的父类加载器是启动类加载器. 它负责加载相对次要, 但又通用的类, 比如存放在 JRE 的 lib/ext 目录下 jar 包中的类(以及由系统变量 java.ext.dirs 指定的类).
应用类加载器的父类加载器则是扩展类加载器. 它负责加载应用程序路径下的类.(这里的应用程序路径, 便是指虚拟机参数 -cp/-classpath, 系统变量 java.class.path 或环境变量 CLASSPATH 所指定的路径.)默认情况下, 应用程序中包含的类便是由应用类加载器加载的.
Java 9 引入了模块系统, 并且略微更改了上述的类加载器 1. 扩展类加载器被改名为平台类加载器(platform class loader).Java SE 中除了少数几个关键模块, 比如说 java.base 是由启动类加载器加载之外, 其他的模块均由平台类加载器所加载. 当然还可以自定义类加载器哦.
除了加载功能之外, 类加载器还提供了命名空间的作用, 在 Java 虚拟机中, 类的唯一性是由类加载器实例以及类的全名一同确定的. 即便是同一串字节流, 经由不同的类加载器加载, 也会得到两个不同的类. 在大型应用中, 我们往往借助这一特性, 来运行同一个类的不同版本.
2. 链接
是指将创建成的类合并至 Java 虚拟机中, 使之能够执行的过程. 它可分为验证, 准备以及解析三个阶段.
验证阶段: 确保被加载类能够满足 Java 虚拟机的约束条件.
准备阶段: 为被加载类的静态字段分配内存, 构造其他跟类层次相关的数据结构.
解析阶段: 将符号引用解析成为实际引用(Java 虚拟机规范并没有要求在链接过程中完成解析. 它仅规定了: 如果某些字节码使用了符号引用, 那么在执行这些字节码之前, 需要完成对这些符号引用的解析).
符号引用则是在 class 文件被加载至 Java 虚拟机之前, 类无法知道其他类及其方法, 字段所对应的具体地址, 甚至不知道自己方法, 字段的地址. 每当需要引用这些成员时, Java 编译器会生成一个符号引用. 在运行阶段, 这个符号引用一般都能够无歧义地定位到具体目标上.
3. 初始化
初始化即给常量赋值以及执行 <clinit> 方法的过程, 完成之后, 类才正式成为可执行的状态.
类初始化触发条件
当虚拟机启动时, 初始化用户指定的主类;
当遇到用以新建目标类实例的 new 指令时, 初始化 new 指令的目标类;
当遇到调用静态方法的指令时, 初始化该静态方法所在的类;
当遇到访问静态字段的指令时, 初始化该静态字段所在的类;
子类的初始化会触发父类的初始化;
如果一个接口定义了 default 方法, 那么直接实现或者间接实现该接口的类的初始化, 会触发该接口的初始化;
使用反射 API 对某个类进行反射调用时, 初始化这个类;
当初次调用 MethodHandle 实例时, 初始化该 MethodHandle 指向的方法所在的类.
总结
虚拟机加载 Java 类是 Java 虚拟机将字节流转化为 Java 类的过程. 这个过程可分为加载, 链接以及初始化三大步骤.
加载: 是指查找字节流, 并且据此创建类的过程. 加载需要借助类加载器, 在 Java 虚拟机中, 类加载器使用了双亲委派模型, 即接收到加载请求时, 会先将请求转发给父类加载器.
链接: 是指将创建成的类合并至 Java 虚拟机中, 使之能够执行的过程. 链接还分验证, 准备和解析三个阶段. 其中, 解析阶段为非必须的.
初始化: 是为标记为常量值的字段赋值, 以及执行 <clinit> 方法的过程. 类的初始化仅会被执行一次, 这个特性被用来实现单例的延迟初始化.
来源: http://www.jianshu.com/p/8cc6291c8e39