JVM类加载过程

JVM类加载过程分为一下几个阶段:

txt 复制代码
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载

加载

类的加载阶段主要涉及到三件事情

  1. 将字节码文件加载到虚拟机的运行时数据区当中
  2. 将字节码的静态存储结构转化为方法区当中的运行时数据结构
  3. 在内存当中生成该类的java.lang.Class对象,作为方法区这个类的数据的访问入口。

在这里值得注意的是,对于第一点所指的字节码加载过程并没有明确的约束,这里可以通过本地文件路径以及网络等多种方式进行加载。

在第二点生成运行时数据结构的过程当中包含了类型信息、常量已经静态变量等,其中还存储了对象的元数据(InstanceKlass)其中记录了一个对象的基本信息如父类信息,属性信息等。

而第三步创建的java.lang.Class对象实际上是对于方法区当中InstanceKlass的一个入口,InstanceKlass是使用C++编写的不能直接暴露在外,因此提供了一个镜像作为访问入口。

验证

验证阶段主要的职责是校验字节码文件是否符合JVM的相关规范,其中的验证包含了对文件格式、元数据以及字节码等的验证。

验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

Java语言本身是相对安全的编程语言(起码对于C/C++来说是相对安全的),使用纯粹的Java代码无法做到诸如访问数组边界以外的数据、将一个对象转型为它并未实现的类型、跳转到不存在的代码行之类的事情,如果尝试这样去做了,编译器会毫不留情地抛出异常、拒绝编译。但前面也曾说过,Class文件并不一定只能由Java源码编译而来,它可以使用包括靠键盘0和1直接在二进制编辑器中敲出Class文件在内的任何途径产生。上述Java代码无法做到的事情在字节码层面上都是可以实现的,至少语义上是可以表达出来的。Java虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有错误或有恶意企图的字节码流而导致整个系统受攻击甚至崩溃,所以验证字节码是Java虚拟机保护自身的一项必要措施。

验证阶段是非常重要的,这个阶段是否严谨,直接决定了Java虚拟机是否能承受恶意代码的攻击,从代码量和耗费的执行性能的角度上讲,验证阶段的工作量在虚拟机的类加载过程中占了相当大的比重。但是《Java虚拟机规范》的早期版本(第1、2版)对这个阶段的检验指导是相当模糊和笼统的,规范中仅列举了一些对Class文件格式的静态和结构化的约束,要求虚拟机验证到输入的字节流如不符合Class文件格式的约束,就应当抛出一个java.lang.VerifyError异常或其子类异常,但具体应当检查哪些内容、如何检查、何时进行检查等,都没有足够具体的要求和明确的说明。直到2011年《Java虚拟机规范(Java SE 7版)》出版,规范中大幅增加了验证过程的描述(篇幅从不到10页增加到130页),这时验证阶段的约束和验证规则才变得具体起来。受篇幅所限,本书中无法逐条规则去讲解,但从整体上看,验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。

准备

准备阶段的主要工作是为类当中定义的静态变量初始化,并为静态变量赋0值

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区本身是一个逻辑上的区域,在JDK 7及之前,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;而在JDK 8及之后,类变量则会随着Class对象一起存放在Java堆中,这时候"类变量在方法区"就完全是一种对逻辑概念的表述了

关于准备阶段,还有两个容易产生混淆的概念笔者需要着重强调,首先是这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次是这里所说的初始值"通常情况"下是数据类型的零值

解析

准备阶段的主要工作是将常量池当中的符号引用转化为直接引用

符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式中

直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在。

在.class字节码文件当中的静态常量池实际上是以编号索引表的结构记录数据:

假设我们有这样一个类 Test.java

java 复制代码
public class Test {
    public void hello() {
        System.out.println("Hi");
    }
}

在字节码文件中,它的逻辑结构长这样(注意左边的 # 编号):

索引 (Key) 类型 (Tag) 内容 / 引用 (Value) 说明
#1 Methodref #6.#15 引用了第6号和第15号内容
#2 Fieldref #16.#17 引用了第16号和第17号内容(System.out)
#3 String #18 引用了第18号内容(字符串 "Hi")
#4 Methodref #19.#20 引用了第19号和第20号内容(println方法)
#5 Class #21 本类的类名索引
#6 Class #22 父类 java/lang/Object 的索引
... ... ... ...
#18 Utf8 Hi 最终的真实字符串值
#21 Utf8 com/demo/Test 最终的类路径字符串

当你调用 System.out.println("Hi") 时,字节码指令并不是直接写着字符串,而是通过层层跳转 找到:字节码里有一条指令 ldc #3。它的意思是:"去常量池找 #3 号元素放进栈里"。JVM 看向 #3 ,发现它是一个 String 类型,指向 #18 。JVM 再看向 #18 ,发现它是一个 Utf8 类型,里面存的正是字节序列 "Hi"

而在解析阶段要做的事情就是将上述的编号索引表转化为一个易查询的Key-Value 映射结构

  • Key(描述消息) :比如方法的完整签名 com/example/Test.hello:()V(类名+方法名+参数返回类型)。
  • Value(对应位置) :目标在内存中的绝对物理地址(指针)

初始化

初始化简单来说就是执行类的clinit方法,也是为数不多可以由应用程序主导的步骤。在准备阶段以及完成了对变量的初始化操作,而初始化步骤是真正为这些静态变量赋值。

对于clinit方法来说它是通过收集类当中的赋值操作以及静态代码块当中的操作合并产生的,并不是程序员自定义的也并非构造方法,它是Javac编译器的自动生成物。

类的初始化阶段是类加载过程的最后一个步骤,之前介绍的几个类加载的动作里,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控制。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。

进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源。我们也可以从另外一种更直接的形式来表达:初始化阶段就是执行类构造器()方法的过程。()并不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成物,但我们非常有必要了解这个方法具体是如何产生的,以及()方法执行过程中各种可能会影响程序运行行为的细节,这部分比起其他类加载过程更贴近于普通的程序开发人员的实际工作[1]。()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问,如代码清单7-5所示。

相关推荐
嘎嘎NULL3 小时前
Gitea配置邮箱
运维·服务器·gitea
wuqingshun3141593 小时前
说一下java的反射机制
java·开发语言·jvm
funnycoffee1234 小时前
Linux查看版本号命令cat /etc/os-release
linux·服务器
xiaoliuliu123455 小时前
Kylin V10 安装 compat-gcc-44-4.4.7-8.el7.x86_64.rpm 详细步骤
linux·运维·服务器
峰顶听歌的鲸鱼5 小时前
Kubernetes-Pod
linux·运维·云原生·容器·kubernetes·云计算
有点心急10215 小时前
Python 入门
服务器·数据库·python
敲代码的哈吉蜂5 小时前
haproxy——socat热更新工具
linux·运维·服务器
wuqingshun3141596 小时前
红黑树有哪些特征
java·开发语言·jvm
wuqingshun3141596 小时前
说一下什么是fail-fast
java·开发语言·jvm