类的加载、链接和初始化(基于Java 1.8)
类的加载、链接和初始化(基于Java 1.8)
Java的数据类型(Data type)主要是有两种:
其中引用类型又被细分为:
- 类 class types
- 数组 array types
- 接口 interface types
基本类型和数组类型是由Java虚拟机直接生成的,其他(类 class types、接口 interface types)则需要Java虚拟机对其进行链接和初始化。
1. Java虚拟机启动 Java Virtual Machine Startup
- 通过引导类加载器创建一个初始类来启动,并执行这个
public class
中的void main(String[])
方法。 - 初始类可以作为命令行参数提供。或者,该实现可以提供一个初始类,该初始类设置一个类加载器,该类加载器进而加载。
2. 创建和加载 Creation and Loading
- 指查找字节流,并据此创建类的过程。
- 其中数组(array types)是没有字节流的,由Java虚拟机直接生成,对于其他的类来说,Java虚拟机需要借助于类加载器来完成查找字节流的工程。
2.1 类加载器 ClassLoader
在Java虚拟机规范中,类加载器被分为两种:
- Java虚拟机提供的引导类加载器(bootstrap class loader)
- 用户定义的类加载器(user-defind class loaders)
2.1.1 Java虚拟机提供的引导类加载器 bootstrap class loader
- bootstrap class loader 由Java虚拟机提供的
- 这个类加载器是使用C++实现的,没有对应的Java对象。
2.1.2 用户定义的类加载器 user-defind class loaders
- user-defind class loaders 是Java虚拟机规范中对于类加载器的分类划分,是一个统称,实际上并没有这个类加载器
- 用户定义的类加载器都是
java.lang.ClassLoader
类的子类 - 在Java虚拟机规范中提到,用户定义的类加载器可以实现通过网络下载类,动态生成类或从加密文件中提取类
- 用户定义的类加载器需要由bootstrap class loader去加载
- 在Java1.8的核心类库中,提供了两个类加载器,分别是:
- 扩展类加载器 extention class loader
sun.misc.Launcher.ExtClassLoader
- 应用类加载器 application class loader
sun.misc.Launcher.AppClassLoader
- 扩展类加载器 extention class loader
2.1.2.1 扩展类加载器 extention class loader
- 扩展类加载器的父是启动类加载器(bootstrap class loader)
- 负责加载相对次要、但又通用的类,如JRE的lib/ext目录下jar包中的类(以及java.ext.dirs指定的类, 这个可以通过查看
sun.misc.Launcher.ExtClassLoader.getExtDirs()
方法确认)
2.1.2.2 应用类加载器 application class loader
- 应用类加载器的父则是扩展类加载器
- 负责加载应用程序路径下的类(应用程序指虚拟机参数-cp/-classpath、系统变量java.class.path或环境变量CLASSPATH所指定的路径。这个可以通过查看
sun.misc.Launcher.AppClassLoader
确认) - 默认情况下,应用程序中包含的类便是通过应用类加载器加载的。
2.1.3 双亲委派模型
指的是一个类加载器接收到加载请求时,会先将请求转发给父类加载器,在父类加载器没有找到所请求的类的情况下,该类加载器才会去尝试加载。
双亲委派模型可以避免类的重复加载,以及java的核心api被篡改的问题。
3 链接 Linking
- 指将创建的类合并至Java虚拟机中,使之能够执行的过程。可分为验证(Verification)、准备(Preparation)以及解析(Resolution)三个阶段
3.1 验证 Verification
验证是为了确保被加载的类满足Java虚拟机的约束条件。
3.2 准备 Preparation
- 准备是为被加载的类的静态字段分配内容。
- 构造其他跟类层次相关的数据结构:如用来实现虚方法的动态绑定的方法表。
3.3 解析 Resolution
在开始解析之前,需要知道:
class文件被加载到Java虚拟机之前,这个类无法知道其他类及其方法、字段所对应的具体地址,甚至不知道自己方法、字段的地址。因此,每当需要引用这些成员时,Java编译器会生成一个符号引用。在运行阶段,这个符号引用一般都能无歧义地定位到具体目标上。
- 解析的目标是将符号引用解析成为实际应用: 如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析就触发这个类的加载。(但未必会出发这个类的链接和初始化)
此外,在Java虚拟机规范中并没有要求在链接过程中完成解析。仅规定了:如果某些字节码使用了符号引用,那么在执行这些字节码之前,需要完成对这些符号引用的解析。
4. 初始化 Initialization
- 为标记为常量值的字段赋值,以及执行
<clinit>
方法的过程 - Java虚拟机会通过加锁来确保类的
<clinit>
方法仅被执行一次
常量值解释:
Java代码中,如果要初始化一个静态字段,可以在声明时直接赋值,或者在静态代码块中对其进行赋值
如果直接赋值的静态字段被final所修饰,并且它的类型是基本类型或字符串时,该字段便会被Java编译器标记为常量值(ConstantValue)
4.1 初始化的触发条件
在Java虚拟机规范中明确枚举了以下情况:
The execution of any one of the Java Virtual Machine instructions new, getstatic, putstatic, or invokestatic that references C (§new, §getstatic, §putstatic, §invokestatic). These instructions reference a class or interface directly or indirectly through either a field reference or a method reference.
Upon execution of a new instruction, the referenced class is initialized if it has not been initialized already.
Upon execution of a getstatic, putstatic, or invokestatic instruction, the class or interface that declared the resolved field or method is initialized if it has not been initialized already.
The first invocation of a java.lang.invoke.MethodHandle instance which was the result of method handle resolution (§5.4.3.5) for a method handle of kind 2 (REF_getStatic), 4 (REF_putStatic), 6 (REF_invokeStatic), or 8 (REF_newInvokeSpecial).
This implies that the class of a bootstrap method is initialized when the bootstrap method is invoked for an invokedynamic instruction (§invokedynamic), as part of the continuing resolution of the call site specifier.
Invocation of certain reflective methods in the class library (§2.12), for example, in class Class or in package java.lang.reflect.
If C is a class, the initialization of one of its subclasses.
If C is an interface that declares a non-abstract, non-static method, the initialization of a class that implements C directly or indirectly.
If C is a class, its designation as the initial class at Java Virtual Machine startup (§5.2).
Java虚拟机规范中有部分是依赖于Java虚拟机指令了,我对此了解并不多,以下摘抄于《极客时间-深入拆解Java虚拟机-郑雨迪》的分享,相较而言更通俗易懂些。
- 当虚拟机启动时,初始化用户指定的主类;
- 当遇到用以新建目标类实例的new指令时,初始化new指令的目标类;
- 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
- 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
- 子类的初始化会触发父类的初始化;
- 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
- 使用反射API对某个类进行反射调用时,初始化这个类;
- 当初次调用MethodHandle实例时,初始化该MethodHandle指向的方法所在的类。
5. 绑定本机方法实现
指的是将Java编程语言以为的其他语言编写的功能和实现native方法的功能集成到Java虚拟机中以便可以执行的过程。
传统上来说,此过程可称为链接,但Java虚拟机规范中指出,使用绑定是为了避免于Java虚拟机对类和接口的链接产生混淆。
6. Java虚拟机退出
当调用Runtime.exit()
、Runtime.halt()
、System.exit
,并且SecurityManager
安全管理其允许exit或halt时候,Java虚拟机就会被关闭。
同时,JNI(Java Native Interface)规范描述了对于JNI调用相关的java虚拟机的终止信息。
参考文档
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
https://time.geekbang.org/column/article/11523