一、JVM基础架构与内存模型
1.1 JVM整体架构概览
Java虚拟机(JVM)是Java程序运行的基石,它由以下几个核心子系统组成:
子系统 | 功能描述 | 类比解释 |
---|---|---|
类加载子系统 | 负责加载.class文件到内存 | 像图书馆管理员,负责把书(类)从书架(磁盘)拿到阅读区(内存) |
运行时数据区 | 程序运行时的内存区域 | 相当于图书馆的不同功能区(阅览区、储物柜等) |
执行引擎 | 解释/编译字节码并执行 | 像翻译官,把Java字节码翻译成机器码 |
本地方法接口 | 调用本地方法库 | 像外语翻译,调用非Java编写的功能 |
垃圾回收系统 | 自动内存管理 | 像清洁工,回收不再使用的内存空间 |
1.2 运行时数据区详解
JVM内存主要分为以下几个区域:
public class MemoryStructure {
static int staticVar = 1; // 方法区(类静态变量)
int instanceVar = 2; // 堆内存(实例变量)
public void method() {
int localVar = 3; // 栈帧中的局部变量表
Object obj = new Object(); // obj引用在栈,对象实例在堆
MemoryStructure mem = new MemoryStructure();
}
}
1.2.1 程序计数器(PC Register)
- 线程私有,记录当前线程执行的字节码行号
- 唯一不会发生OOM的区域
1.2.2 Java虚拟机栈(VM Stack)
栈用于存储局部变量和方法调用的信息。可以把栈想象成一个 “千层饼”,每一层代表一个方法的调用,当方法调用结束后,该层就会被移除。
- 线程私有,存储栈帧(Stack Frame)
- 栈帧包含:局部变量表、操作数栈、动态链接、方法返回地址
public class StackExample {
public static void main(String[] args) {
int a = 1; // 局部变量表slot 0
int b = 2; // 局部变量表slot 1
int c = add(a, b); // 创建新的栈帧
}
public static int add(int x, int y) {
int sum = x + y; // 新栈帧的局部变量表
return sum;
}
}
在上述代码中,main
方法和 add
方法的局部变量(如 a
、b
、x
、y
)以及方法调用信息都存放在栈中。
1.2.3 本地方法栈(Native Method Stack)
- 为本地方法服务,类似虚拟机栈
- HotSpot中将虚拟机栈和本地方法栈合二为一
1.2.4 堆内存(Heap)
堆是 JVM 中最大的一块内存区域,用于存储对象实例。可以把堆想象成一个大仓库,所有的对象都存放在这里。
- 线程共享,存放对象实例
- GC主要工作区域
- 可分为新生代(Eden, Survivor)、老年代
// 创建一个对象,存放在堆中
public class HeapExample {
public static void main(String[] args) {
// 创建一个Person对象,存放在堆中
Person person = new Person();
}
}
class Person {
private String name;
private int age;
// 构造方法
public Person() {
this.name = "John";
this.age = 30;
}
}
在上述代码中,new Person()
创建的 Person
对象实例就存放在堆中。
1.2.5 方法区(Method Area)
方法区用于存储类的信息、常量、静态变量等。可以把方法区想象成一个 “知识库”,存储着类的各种知识和规则。
- 存储类信息、常量、静态变量等
- JDK8后由元空间(Metaspace)实现,使用本地内存
public class MethodAreaExample {
// 静态变量,存放在方法区
public static final String MESSAGE = "Hello, World!";
public static void main(String[] args) {
System.out.println(MESSAGE);
}
}
在上述代码中,MESSAGE
静态常量存放在方法区。
1.3 内存区域对比
内存区域 | 线程共享 | 是否GC | 可能异常 | 配置参数 |
---|---|---|---|---|
程序计数器 | 私有 | 否 | 无 | 无 |
虚拟机栈 | 私有 | 否 | StackOverflowError/OutOfMemoryError | -Xss |
本地方法栈 | 私有 | 否 | StackOverflowError/OutOfMemoryError | 同虚拟机栈 |
堆内存 | 共享 | 是 | OutOfMemoryError | -Xms, -Xmx, -Xmn |
方法区 | 共享 | 是 | OutOfMemoryError | -XX:MetaspaceSize |
二、类加载机制与字节码执行
2.1 类加载过程
类加载的过程包括加载、连接(验证、准备、解析)和初始化。可以把类加载的过程想象成一场演出的筹备过程,加载就像是邀请演员(类),连接就像是安排演员的服装、道具等,初始化就像是演员正式登台表演。
类加载分为以下五个阶段:
阶段 | 功能描述 | 通俗解读 |
---|---|---|
加载 | 通过类的全限定名获取类的二进制字节流,并将其转换为方法区中的运行时数据结构,在堆中创建对应的 Class 对象 |
邀请演员到演出场地 |
验证 | 确保字节码文件的正确性和安全性 | 检查演员的身份和资质 |
准备 | 为类的静态变量分配内存并设置初始值 | 为演员准备服装和道具 |
解析 | 将符号引用转换为直接引用 | 确定演员在舞台上的具体位置 |
初始化 | 执行类的静态代码块和静态变量的赋值操作 | 演员正式登台表演 |
public class ClassLoadExample {
static {
System.out.println("静态代码块执行"); // 初始化阶段执行
}
public static int value = 123; // 准备阶段赋0,初始化阶段赋123
public