JVM工作原理与实战(六):类的生命周期-连接阶段

简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了类的生命周期、类的连接阶段等内容。

一、类的生命周期

类的生命周期描述了一个类加载、连接、初始化、使用、卸载的整个过程。


1.加载(Loading)

加载阶段是类的生命周期的起始点。当应用程序首次需要使用某个类时,Java虚拟机(JVM)会负责加载这个类。加载是通过类的加载器(ClassLoader)完成的,它会查找并加载类的二进制数据。这个过程包括将类的字节码从文件系统、JAR文件或网络加载到内存中。

2.连接(Linking)

连接阶段是加载阶段的后续,它包括验证、准备和解析三个子阶段。

  • 验证(Verification):验证阶段主要是确保被加载的类文件数据符合JVM规范,没有安全方面的隐患,以及是否与应用程序的其它部分兼容。验证过程包括文件格式验证、元数据验证、字节码验证和符号引用验证。
  • 准备(Preparation):准备阶段是为类的静态变量分配内存,并设置默认的初始值。需要注意的是,准备阶段并不会执行任何初始化操作。
  • 解析(Resolution):解析阶段是将符号引用转换为直接引用。在Java中,符号引用是一个类的全限定名,而直接引用是一个直接指向内存中的地址的指针。解析阶段发生在运行时,而不是编译时。

3.初始化(Initialization)

初始化阶段是类加载过程中的最后一步,当准备和解析阶段完成后,JVM会执行类的构造器方法,这个方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块集合来的。需要注意的是,构造器方法中的代码只在类被首次使用时执行一次。

4.使用(Using)

一旦类被成功加载、连接并初始化后,就可以被实例化并用于执行应用程序的业务逻辑。在应用程序运行期间,类可能会被频繁地使用。

5.卸载(Unloading)

当应用程序不再需要某个类时,该类的实例以及与其相关的资源将会被回收,这个过程就是卸载。但是需要注意的是,只有当一个类不再被任何活动对象所引用时,它才会被卸载。另外,JVM的垃圾回收机制(Garbage Collection, GC)负责自动处理类的卸载和资源的回收。

二、连接阶段

1.验证

在Java类的生命周期中,连接阶段是一个至关重要的环节,它确保了Java字节码文件在被Java虚拟机(JVM)加载前满足一定的规范和要求。连接阶段的首要任务是验证,这一过程对Java字节码文件进行了严格的检查,以确保其遵守《Java虚拟机规范》中定义的各种约束。这一验证过程通常对程序员是透明的,不需要他们直接参与。

验证过程主要包括以下四个部分:

  • 文件格式验证:这是验证的第一步,主要检查字节码文件的基本格式。例如,它会验证文件是否以特定的魔数(magic number)0xCAFEBABE开头,这是Java类文件的标识。此外,还会检查文件的主次版本号是否与当前Java虚拟机的版本兼容。版本号的检查是确保类文件是用与当前JVM兼容的Java编译器编译的。


  • 元数据验证:在这一步中,验证器会检查类的元数据信息。这包括类的继承关系、接口实现、字段和方法的存在性和访问权限等。例如,验证器会确保每个类都有父类(除了java.lang.Object),并且类的继承层次结构没有出现问题。此外,还会检查方法的字节码,确保它们不会执行非法的操作,如跳转到不正确的位置。


  • 字节码验证:这是最复杂的一步,验证器会深入分析方法的字节码,确保它们符合Java虚拟机的语义规则。这个过程会检查诸如类型安全、操作数栈的数据流和使用情况等。字节码验证的目的是防止潜在的恶意代码或由于编译器错误导致的无效代码被执行。
  • 符号引用验证:在这一步中,验证器会检查类文件中的符号引用。符号引用是类在编译时对其他类、方法或字段的引用,这些引用在类加载时会被解析为实际的内存地址。验证器会确保这些符号引用是有效的,例如,不会访问其他类的私有方法或不存在的字段。

在Hotspot JDK 8的虚拟机源码中,版本号的检测是通过一段特定的代码来实现的。这段代码确保了主版本号(major version)和副版本号(minor version)都在Java虚拟机支持的范围内。具体来说,主版本号不能高于运行环境的主版本号,如果主版本号相等,则副版本号也不能超过运行环境所支持的最大副版本号。这样的版本号检测机制确保了类文件与运行环境的兼容性。

Hotspot JDK8中虚拟机源码对版本号检测的代码如下:

return (major >= JAVA_MIN_SUPPORTED_VERSION) && (major <= max_version) && ((major != max_version) || (minor <= JAVA_MAX_SUPPORTED_MINOR_VERSION));

image.gif

major >= JAVA_MIN_SUPPORTED_VERSION major(主版本号)大于或等于最小支持的Java版本
major <= max_version major(主版本号)小于或等于最大支持的Java版本

(major != max_version) ||

(minor <= JAVA_MAX_SUPPORTED_MINOR_VERSION)

major(主版本号)不是最大支持版本,或者minor(次版本号)在最大支持范围内

验证阶段是Java类加载过程中非常重要的一环,它确保了只有符合规范的类文件才能被Java虚拟机加载和执行。这一过程不仅增强了Java平台的安全性,还提高了代码的健壮性和可移植性。

2.准备

准备阶段的主要任务是为类的静态变量分配内存,并设置这些变量的初始值。准备阶段只会为静态变量赋予初始值,而不是最终的值。每一种基本数据类型和引用数据类型在准备阶段都有其特定的初始值。

以下是基本数据类型和引用数据类型的初始值列表:

数据类型 初始值
int 0
long 0L
short 0
char ‘\u0000’
byte 0
boolean false
double 0.0
引用数据类型 null

这些初始值是Java虚拟机规范所规定的,它们在准备阶段被自动赋予给相应的静态变量。

然而,有一个特殊的情况需要注意,那就是被final修饰的基本数据类型的静态变量。在准备阶段,如果静态变量被final修饰,并且其值在编译时就已经确定,那么Java虚拟机将直接将该值赋给静态变量,而不是赋予初始值。这一特性使得被final修饰的静态变量在准备阶段就能获得其最终的值。

下面通过两个示例来说明这一点:

示例一(类Test包含一个普通的静态变量i):

public class Test {
    public static int i = 1;
    public static void main(String[] args) {
    }
}

image.gif

对于这个示例,在准备阶段,静态变量i会被赋予其初始值0,而不是最终值1,最终值1的赋值发生在初始化阶段。


示例二(类Test包含一个被final修饰的静态变量i):

public class Test {
    public static final int i = 1;
    public static void main(String[] args) {
    }
}

image.gif

对于这个示例,在准备阶段,静态变量i会被直接赋予其最终值1,因为它是一个编译时常量。这意味着在准备阶段完成后,静态变量i就已经获得了其最终的值,而不需要等到初始化阶段。


在Java类的生命周期的连接阶段中,准备阶段是一个关键步骤,它负责为静态变量分配内存并设置初始值。对于被final修饰的静态变量,如果其值在编译时就已经确定,那么准备阶段将直接赋予其最终值。这一特性为Java程序员提供了一种优化静态变量初始化的手段。

3.解析

解析阶段作为连接阶段的一部分,其主要任务是将常量池中的符号引用转换为直接引用

符号引用:

在Java字节码中,常量池用于存储各种常量,如字符串、类名等。这些常量在常量池中通过编号进行索引。在字节码文件中,这些索引被用作符号引用。例如,当我们在字节码中引用一个类时,实际上是通过一个在常量池中的索引来引用该类,这个索引被称为类符号引用。同样地,字段和方法的引用也是通过相应的符号引用来表示的。


直接引用:

与符号引用不同,直接引用是直接指向目标对象的指针或地址。这意味着直接引用是具体的、指向内存中的某个位置的地址。通过直接引用,JVM可以直接定位并访问目标对象,而不必通过一系列的索引和查找操作。


解析过程:

在解析阶段,JVM将常量池中的符号引用转换为直接引用,这一过程是由JVM自动完成的。JVM在解析阶段会遍历字节码中的指令,将遇到的符号引用替换为直接引用。这个过程涉及到在运行时解析符号引用,并获取目标对象的实际内存地址。

举个例子,如果字节码中有一个对某个类的字段的访问指令,那么在解析阶段,JVM会找到该字段的实际内存地址,并将该地址作为直接引用存储在相应的指令中。这样,当执行该指令时,JVM可以直接访问该字段,而不需要通过查找常量池来获取符号引用。

解析阶段是连接阶段中的关键环节之一,它确保了JVM能够高效地访问和操作目标对象。通过将符号引用转换为直接引用,JVM能够提高指令执行的速度并降低内存开销。这也是Java虚拟机实现高效运行的重要手段之一。


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了类的生命周期、类的连接阶段等内容,希望对大家有所帮助。

相关文章
|
5月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
495 55
|
3月前
|
存储 Java 编译器
深入理解Java虚拟机--类文件结构
本内容介绍了Java虚拟机与Class文件的关系及其内部结构。Class文件是一种与语言无关的二进制格式,包含JVM指令集、符号表等信息。无论使用何种语言,只要能生成符合规范的Class文件,即可在JVM上运行。文章详细解析了Class文件的组成,包括魔数、版本号、常量池、访问标志、类索引、字段表、方法表和属性表等,并说明其在Java编译与运行过程中的作用。
|
5月前
|
Oracle Java 关系型数据库
JVM深入原理(一+二):JVM概述和JVM功能
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行。
137 0
|
5月前
|
Arthas 存储 Java
JVM深入原理(三+四):JVM组成和JVM字节码文件
目录3. JVM组成3.1. 组成-运行时数据区3.2. 组成-类加载器3.3. 组成-执行引擎3.4. 组成-本地接口4. JVM字节码文件4.1. 字节码文件-组成4.1.1. 组成-基础信息4.1.1.1. 基础信息-魔数4.1.1.2. 基础信息-主副版本号4.1.2. 组成-常量池4.1.3. 组成-方法4.1.3.1. 方法-工作流程4.1.4. 组成-字段4.1.5. 组成-属性4.2. 字节码文件-查看工具4.2.1. javap4.2.2. jclasslib4.2.3. 阿里Arthas
97 0
|
5月前
|
存储 安全 Java
JVM深入原理(五):JVM组成和JVM字节码文件
类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析。
72 0
|
5月前
|
Arthas Java 测试技术
JVM深入原理(六)(一):JVM类加载器
目录6. JVM类加载器6.1. 类加载器-概述6.2. 类加载器-执行流程6.3. 类加载器-分类(JDK8)6.3.1. JVM底层实现的类加载器6.3.1.1. 启动类加载器6.3.2. Java代码实现类的加载器6.3.2.1. 扩展类加载器6.3.2.2. 应用程序类加载器6.4. 类加载器-Arthas查看类加载器
84 0
|
5月前
|
Java 关系型数据库 MySQL
JVM深入原理(六)(二):双亲委派机制
自定义类加载器打破双亲委派机制的方法:复写ClassLoader中的loadClass方法常见问题:要加载的类名如果是以java.开头,则会抛出安全性异常加载自定义的类都会有一个共同的父类Object,需要在代码中交由父类加载器去加载自定义类加载器不手动指定parent会默认指定应用类加载两个自定义类加载器加载同一个类会被认为是两个对象,只有相同的类加载器+想通的类限定名才会被认为是一个对象。
208 0
|
5月前
|
存储 安全 Java
JVM深入原理(七)(一):运行时数据区
栈的介绍:Java虚拟机栈采用栈的数据结构来管理方法调用中的基本数据,先进后出,每一个方法的调用使用一个栈帧来保存栈的组成:栈:一个线程运行所需要的内存空间,一个栈由多个栈帧组成栈帧:一个方法运行所需要的内存空间活动栈帧:一个线程中只能有一个活动栈帧栈的生命周期:栈随着线程的创建而创建,而回收会在线程销毁时进行栈的执行流程:栈帧压入栈内执行方法执行完毕释放内存若方法间存在调用,那么会压入被调用方法入栈,执行完后释放内存,再执行当前方法,直到执行完毕,释放所有内存。
90 0
|
6月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
484 6
|
9月前
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
941 166