04_jvm_02_initialization

第四十八章 Java虚拟机的初始化过程

1 概述

Java虚拟机是一个重型的、复杂的虚拟运行环境。这个“重”字不仅体现在Java虚拟机复杂的内部构造和运行原理,它还体现在Java虚拟机的初始化过程中。与其他类似的编程语言相比(例如:Python),Java虚拟机的启动是较慢的,因为,在这个启动过程中,Java虚拟机需要完成许多任务。这些任务可大致分为三个部分,它们分别是:加载(Loading),链接(Linking),和初始化(Initialization)。本章将在下面的小节中分别介绍Java虚拟机的这三个启动步骤和停止过程。为了使本章的逻辑结构清晰,类加载器(Class Loader)的具体原理和使用方法将在下一章节详细介绍。我们将在第三十六章介绍.class文件的内部结构。

2 加载过程 (Loading)

在Java虚拟机启动后,Java虚拟机会首先根据配置的参数或者命令行传递的参数,搭建内部结构,在各个区域(调用栈、堆、方法区、常量区等)分配完毕后,Java虚拟机开始加载.class文件。这些.class文件包括Java开发环境提供的标准代码库和由CLASSPATH中指定的类或者.jar文件。一般情况下,这些类是由Java虚拟机内置的类加载器加载的。然而,Java虚拟机还为开发人员提供了实现自定义类加载器(User-defined Class Loader)的接口。(我们将在下一章详细介绍其使用方法。)

Java虚拟机的规范并没有规定此时需要加载多少个.class文件,这可由具体的Java虚拟机的实现决定。但是,可以肯定的是,在此时,Java虚拟机需要加载main函数所在的类,并且可以从main函数开始运行程序。当程序进入main函数运行之后,程序的后续运行就完全按照main函数中的逻辑运行了。如果遇到尚未加载的类时,类加载器会首先加载该类,然后,再完成相应的函数调用或者引用。

当类加载器在解析一个.class文件时,类加载器会在堆中创建一个对应的Class类对象,用于表达该类;并将其常量和方法定义存放在Java虚拟机的常量区和方法区。如果当解析一个.class文件失败时,Java虚拟机并不会立刻报告错误。只有当该类被使用时才会触发错误报告。

3 链接过程 (Linking)

链接过程是一个将各个类信息整合在一起的过程。这个过程包括检查.class文件版本、检查.class文件的内部结构以及其父类/父接口信息、解析成员的语义等环节。例如,当程序调用类Example的方法display()时,Java虚拟机会首先查找Example类对应的Class类对象,读取Example类的所有父类和实现的父接口信息,然后解析所有的成员函数的语义,并分析这个调用是否合法(例如:匹配参数)。

Java虚拟机规范并没有规定这个链接过程需要何时进行。它可以在类加载完毕后立刻执行;也可以在该类被使用时执行。但是,Java虚拟机规定,只有当一个.class文件被完全加载后,Java虚拟机才能进行链接过程。只有当链接过程完毕之后,才能进行下一步初始化过程。

链接过程可能和加载过程交叉运行。例如,当Java虚拟机链接Example类时,Java虚拟机会检查其父类ExampleBase。如果此时ExampleBase类尚未加载,则Java虚拟机会先加载ExampleBase类,并进入其链接过程。如果ExampleBase还有父类的话,这个加载和链接的过程会持续进行下去,直到Example的继承结构中所有类和接口全部被加载和链接。

下面我们简单的介绍一下链接过程中的几个重要步骤。

  1. 检验(Verification)。这个步骤检查加载的.class文件包含的数据是否正确、完整。如果在检验过程中失败的话,Java虚拟机会抛出LinkageError错误。如果.class文件中的内容违反了某种限制条件的话,Java虚拟机会抛出VerifyError错误。
  2. 准备(Preparation)。当检验过程通过后,在准备过程中,Java虚拟机会为新类创建静态变量(Static Variable),并将其设置为默认值。在这个步骤中,Java虚拟机并不会运行任何代码,所以,这些静态变量并不会被设置为初始值。Java虚拟机也不会运行静态代码块中的代码。
  3. 解析(Resolution)。解析过程主要是解析程序中使用的符号引用(Symbolic Reference)。这些常量包括:类/接口的名字,成员变量和成员方法的名字,方法的句柄,和动态计算的常量。
  4. 访问控制检查(Access Control)。访问控制检查是与解析同时完成的。访问控制检查主要检查类、接口、或者成员方法是否能够被访问(例如:私有方法(Private Method)只能在本类代码中访问)。
  5. 方法覆盖(Method Overriding)和方法选择(Method Selection)。方法覆盖与方法选择也是与解析同时完成的。方法覆盖的分析主要针对在某一个方法调用处,分析哪些方法可称为备选方法(Method Candidate)。而方法选择则是根据方法调用的上下文,从备选方法中选择一个最合适的方法作为调用对象。

4 初始化过程 (Initialization)

在初始化过程中,Java虚拟机会运行类和接口的初始化方法(Initialization Method)。在初始化完毕后,类的静态变量会被赋予初始化的值。静态代码块(Static Code Block)也会被运行。总之,当一个类在初始化完毕后,Java程序就可以正式的使用这个类了。

5 退出过程 (Exit)

Java虚拟机规范并没有很多内容去介绍Java虚拟机的退出过程。当Java程序调用Runtime.exit()方法,System.exit()方法,或者Runtime.halt()方法时,Java虚拟机会退出。另一种情况就是当Java程序运行完毕时(当Java程序自然结束且未调用System.exit()方法时),即所有的非守护线程(Non-Daemon Thread)退出时,Java虚拟机会退出。

6 总结

本章介绍了Java虚拟机的初始化过程。因为Java虚拟机的实现版本有很多,每个版本的实现均不相同。然而,大致上讲,Java虚拟机的启动可分为三个步骤:加载、链接、和初始化。除了Java虚拟机内置的类加载器以外,Java虚拟机还允许开发人员实现自定义类加载器(User-defined Class Loader)。我们将在下一章详细介绍类加载器的原理和实现自定义类加载器的细节。

 

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.