本文和大家聊聊 JVM 的基本概念和类加载子系统。
JVM(Java 虚拟机)是一种在计算机上运行 Java 字节码的虚拟机。它是 Java 语言的核心组件之一,允许 Java 程序在不同的硬件平台上执行,实现了“一次编写,到处运行” 的理念。
JVM 的设计理念是提供一个虚拟的执行环境,使得 Java 程序具有跨平台的特性,不受具体硬件和操作系统的限制。通过将 Java 源代码编译成字节码,再由 JVM 解释执行或编译成机器码执行,Java 程序可以在不同的平台上运行,而不需要重新编写和编译。

JVM 主要有以下几个重要组成部分:

类加载子系统是 Java 虚拟机(JVM)的一个重要组成部分,负责加载类文件并将其转换为 JVM 中的可用结构。它主要有以下几个主要功能:
类加载子系统的加载阶段是 JVM 类加载过程的第一阶段。在这个阶段,JVM 会做以下几件事:
java.lang.String)获取定义此类的二进制字节流。java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。加载阶段完成后,JVM 会进入链接阶段,包括验证、准备和解析三个子阶段。
类加载子系统的链接阶段是 JVM 类加载过程的第二阶段。在这个阶段,JVM 会做以下几件事:
OxCAFEBABE 开头,主版本和副版本号是否在当前 Java 虚拟机的支持范围内,数据中每一个项是否都拥有正确的长度等。NoClassDefFoundError,如果一个方法无法被找到,则会抛出 NoSuchMethodError。static final 修饰的情况,因为 final 在编译的时候就会分配了,准备阶段会显式赋值。链接阶段完成后,JVM 会进入初始化阶段。
类加载子系统的初始化阶段是 JVM 类加载过程的最后一阶段。在这个阶段,JVM 会做以下几件事:
() 的过程。这个方法不同于类的构造器(是虚拟机视角下的 () )。初始化阶段完成后,类加载过程就结束了,我们就可以使用这个类来创建对象和调用方法了。
假设我们有以下代码:
public class Test {
public static void main(String[] args) {
SymbolicReference symbolicReference = new SymbolicReference();
symbolicReference.helloWorld();
}
}
class SymbolicReference {
public void helloWorld() {
System.out.println("Hello World");
}
}
上述代码中,Test 类引用了 SymbolicReference#helloWorld() 方法。在编译 Test 类时,编译器并不知道SymbolicReference#helloWorld() 方法在内存中的实际地址,因此只能使用符号引用来代替(即 symbolicReference.helloWorld())。符号引用是一种抽象的引用方式,它通过一组符号(如类名、方法名和描述符)来表示要引用的目标。
在类加载的解析阶段,JVM 会将这些符号引用转换为直接引用。直接引用是一种具体的引用方式,它直接指向内存中的对象或方法的地址。在这个示例中,直接引用就是 SymbolicReference#helloWorld() 方法的实际内存地址。
在 JVM 的类加载过程中,初始化阶段通常是在解析阶段之后进行的。然而,在某些情况下,解析阶段可能会在初始化阶段之后进行。这主要取决于 JVM 的具体实现和运行时的动态链接需求。
以下情况可能会导致初始化阶段在解析阶段之前:
main() 方法的那个类),虚拟机会先初始化这个主类。java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。虽然解析阶段通常在初始化阶段之前,但在某些情况下,解析阶段可能会在初始化阶段之后进行。