深入理解JVM-Java类如何被加载
类加载器从应用程序和Java API加载类文件。只有运行中的程序实际需要Java API中的类的时候才会被加载到虚拟机。

加载
- 加载是指查找字节流,并且据此创建类的过程。除了数组类没有对应的字节流是由JVM直接生成的。其他类,JVM则需要借助类加载器来完成查找字节流的过程。

在JVM中,类的唯一性是由类加载器实例以及类的全名一同决定的。即便是同一串字节流,经由不同的类加载器加载,也会得到不同的类。在大型应用中我们往往借助这一特性,来运行同一个类的不同版本。
启动类加载器(Bootstrap class loader)
- 由C++实现,没有对应的Java对象,因此在Java中只能用null来指代。
- 除了Bootstrap类加载器其他类加载器都是ClassLoader的子类,因此有对应的Java对象。这些类加载器都必须先由另外一个类加载器,比如启动类加载器加载至JVM当中,方能执行类加载。
- Java9之前启动类加载器只负责加载最为基础,最为重要的类:JRE的lib目录下的jar(以及虚拟机参数 -Xbootclasspath指定的类)。
扩展类加载器(Extension class loader)
- 扩展类加载器的父类加载器是启动类加载器。
- 负责加载相对次要、但又通用的类,比如存放在JRE的lib/ext目录下的jar以及由系统变量java.ext.dirs指定的类。
应用类加载器(Application class loader)
- 应用类加载器的父类加载器是扩展类加载器。
- 负责加载应用程序路径下的类。虚拟机参数 -cp/-classpath,系统变量 java.class.path或者环境变量CLASSPATH所指定的类。
Java 9引入了模块系统,并且略微更改了上述类加载器。扩展类加载器被更改为**平台类加载器(platform class loader)**。Java SE中除了少数几个关键模块,比如java.base是由启动类加载器加载之外,其他模块均由平台类加载器所加载。
自定义类加载器
除了JVM提供的类加载器外,可以自定义类加载器,通过继承ClassLoader类实现,主要重写findClass方法。实现特殊的加载方式。举例:对class文件进行加密,加载时再利用自定义类加载器对其进行解密。
双亲委派模型
在JVM中,如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给自己的父类加载器完成。每个类加载器都是如此,只有当自己的父类加载器在自己负责加载的范围内找不到指定类(ClassNotFoundException)时,子类才会进行自己去尝试加载。
作用:
- 提高安全性:防止覆盖系统类库中的类,提高安全性。
- 防止程序混乱:重复加载。
链接
验证
确保被加载的类符合JVM规范。
准备
为被加载类中的静态字段分配内存。
解析
在 class 文件被加载至 Java 虚拟机之前,这个类无法知道其他类及其方法、字段所对应的具体地址,甚至不知道自己方法、字段的地址。因此,每当需要引用这些成员时,Java 编译器会生成一个符号引用。在运行阶段,这个符号引用一般都能够无歧义地定位到具体目标上。
解析阶段的目的是将符号引用解析成为实际引用。如果符号引用指向一个未被加载的类,或者未被加载的字段或者方法。那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化)。
初始化
类加载的最后一步是初始化,便是为标记为常量值的字段赋值,以及执行 < clinit > 方法的过程。Java 虚拟机会通过加锁来确保类的 < clinit > 方法仅被执行一次。
- 当虚拟机启动时,初始化用户指定的主类;
- 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;
- 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
- 当遇到访问静态字段的指令时,初始化该静态字段所在的类;子类的初始化会触发父类的初始化;
- 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
- 使用反射 API 对某个类进行反射调用时,初始化这个类;
- 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。