-
加载阶段
- 加载阶段需要虚拟机做三件事:
- 虚拟机通过一个类的全限定名来获取描述定义它的二进制字节流
- 将其字节流的静态存储结构转换为方法区的运行时数据结构
- 在堆中生成一个对应着该类的java.lang.Class对象作为程序访问方法区中该类型的访问入口
- 数组类型与非数组类型在加载阶段有所区别
- 非数组类型加载阶段比较自由,既可以使用虚拟机内置的引导类加载器也可以使用自定义的类加载器。开发人员可以通过自定义的类加载器控制字节流的获取(自定义类加载器需要重写findClass()和loadClass()方法)
- 数组类型不需要类加载器来创建,它是由java虚拟机内存中动态构造出来的,但是数组类的元素类型最终还是需要由类加载器来完成其加载
- 加载之后虚拟机外部的二进制字节流会按照虚拟机设定的格式存储于内存中,之后会在堆中创建一个与该类相对应的java.lang.Class类的对象用于该类型在方法区中被各种数据的访问外部接口
- 加载阶段会与解析阶段混合进行,夹杂在加载阶段的一些操作属于解析阶段,但这并不影响二者的开始时间和执行顺序
-
验证阶段
- 连接阶段第一步;验证包含类的内容class文件的全部内容是否符合Java虚拟机规范,其内容作为代码运行于虚拟机中是否会危害虚拟机自身安全;验证阶段需要非常关键(承上启下),直接决定虚拟机是否能够承受恶意代码的攻击
- 从整体上来看可以分为四个部分
- 文件格式验证:验证class文件,判断其版本
- 主次版本号是否能被当前虚拟机所接受,并处于其处理范围
- 常量池中的所有常量是否存在不被允许的常量类型
- class文件中的各个数据项以及本身是否被篡改
- 元数据验证:查看程序是否符合java语义规范
- 该类是否有父类(虽然java不许多继承但是除了java.lang.Object没有父类之外,都该有)
- 该类的父类是否继承了不被允许继承的类(被final修饰等等)
- 若非抽象类,是否实现了来自父类/接口中的所有方法
- 字节码验证:通过控制流分析和数据流分析,判断程序语义是否符合逻辑概念,对类的方法体(Class文件中的Code属性(代码))进行校验,确保类中代码在虚拟机中执行不会危害虚拟机安全
- 操作数栈放置了一个float类型数据,却按照double类型存入本地变量表中;这种操作不允许出现
- 跳转指令跳转到了方法体以外的字节码指令上也是不被允许的
- 符号引用验证:检查类是否缺少或者被禁止访问它所依赖的外部类或者是字段,方法,接口等资源
- 符号引用中的字符串描述的全限定名是否能够定位到类
- 符号引用中的字段、方法、接口的可访问度,是否能被当前类访问
- 文件格式验证:验证class文件,判断其版本
- 验证阶段不是必须要执行,如果程序的所有代码都已经验证过执行过很多次,可以关闭大部分的类验证,节省类加载过程的效率
-
准备阶段
- 连接阶段第二步;对类中所定义的变量(static变量,即静态变量)进行内存分配并设置初始值的过程
- 从概念上来看,应在方法区中分配内存,但方法区是逻辑上的区域
- JDK7及之前,虚拟机团队使用永久代实现方法区,实现方法区的逻辑概念
- JDK8及之后,类变量会随着class对象一起分配到Java堆中
- 易混淆之二:
- 此时分配内存的只有类变量没有实例变量,实例变量会在实例初始化时被放到Java堆中
- 通常情况下初始值为该数据类型的零值
- 不太通常的情况下,类字段的字段属性中包含ContantsValue属性时,会将初始值设置为该属性的变量值
-
解析阶段
- 连接阶段最后一步;解析阶段是将常量池中的符号引用替换为直接引用的过程
- 发生时间:虚拟机规范中并未强制解析何时执行,只描述了在执行17条指令(new、getStatic、putStatic、invokeStatic、invokeDynamic等)之前,对常量池中的符号因用进行解析。虚拟机会根据需要自行判断采用何种方式:是在类被类加载器加载前对常量池中的符号引用进行解析还是在符号引用即将被引用之前再解析
- 关于缓存:
- 每个符号引用都有可能会被解析多次,除invokeDynamic指令之外,虚拟机会将第一次解析结果缓存下来
- invokeDynamic指令会在程序快要执行到这行代码时才会解析,其引用被称为动态调用点限定符
- 解析动作*7:类或接口、字段、类方法、接口方法、方法类型、方法句柄、动态调用点
-
初始化阶段
- 类加载过程最后一步;在此阶段虚拟机将开始执行类中的代码,并将主导权移交给应用程序
- 与其说是初始化阶段不如说是类构造器<clinit>()方法的执行过程,该方法是javac编译器的自动生成物
- <clinit>()方法由编译器自动收集类中的变量的复制动作以及静态语句块(static{})的语句组成;收集顺序由语句在源文件中的顺序决定;静态语句块只能访问自己以及之前的变量,不能访问静态语句块之下的内容,但可以在static块中为其赋值