JVM运行时数据区核心架构
JVM的逻辑架构中,运行时数据区是内存管理的核心载体,按照线程共享特性可以分为两类:线程私有区域和线程共享区域。线程私有区域包括程序计数器、虚拟机栈、本地方法栈,随线程创建而分配,随线程销毁而回收;线程共享区域包括堆和方法区,随虚拟机启动而创建,随虚拟机退出而销毁。不同类型的变量会根据自身特性存储在不同的区域,其生命周期也和所在区域的特性强关联。

各区域功能与变量存储特性
- 程序计数器:线程私有,记录当前线程执行的字节码行号,不存在变量存储,无生命周期相关的变量映射。
- 虚拟机栈:线程私有,每个方法执行时会创建栈帧,栈帧中的局部变量表用于存储方法内的局部变量,是局部变量的核心存储区域。
- 本地方法栈:线程私有,服务于本地方法,一般不直接存储Java层面的变量。
- 堆:线程共享,是对象实例的存储区域,实例变量作为对象的属性,随对象存储在堆中。
- 方法区:线程共享,存储类信息、常量、静态变量等,静态变量和常量直接存储在该区域。
不同类型变量的生命周期映射分析
局部变量:虚拟机栈中的短周期映射
局部变量是定义在方法内部、代码块内部的变量,包括基本数据类型和对象引用类型。局部变量存储在虚拟机栈的栈帧局部变量表中,其生命周期和方法的执行周期完全一致。
当方法被调用时,JVM会为该方法创建对应的栈帧,局部变量表在栈帧初始化时分配内存,局部变量进入存活状态;当方法执行结束,栈帧出栈销毁,局部变量表占用的内存被回收,局部变量生命周期结束。
下面通过一个简单的方法示例说明局部变量的生命周期:
public class LocalVariableDemo {
public void calculate() {
// 局部变量a,基本数据类型,存储在当前方法栈帧的局部变量表
int a = 10;
// 局部变量obj,对象引用类型,引用本身存储在局部变量表,指向的Object对象存储在堆
Object obj = new Object();
// 方法执行过程中,a和obj引用都处于存活状态
System.out.println(a);
System.out.println(obj);
// 方法执行结束,栈帧出栈,a和obj引用被销毁,obj指向的对象如果没有其他引用会被GC回收
}
}
实例变量:堆中对象关联的中周期映射
实例变量是定义在类中、方法外的非静态变量,属于对象的属性。实例变量随对象实例存储在堆中,其生命周期和所属对象的生命周期完全一致。
当通过new关键字创建对象时,堆中会为对象分配内存空间,实例变量作为对象的一部分同时完成初始化,进入存活状态;当对象没有任何引用指向时,会被垃圾回收器标记为可回收,对象被回收后,实例变量占用的内存也随之释放,生命周期结束。
实例变量的生命周期示例代码如下:
public class InstanceVariableDemo {
// 实例变量name,属于对象属性,存储在堆中的对象实例里
private String name;
// 实例变量age,基本数据类型作为实例变量时,也会随对象存储在堆中
private int age;
public InstanceVariableDemo(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
// 创建对象,实例变量name和age随对象在堆中分配内存,进入存活状态
InstanceVariableDemo demo = new InstanceVariableDemo("test", 20);
// demo引用指向对象,实例变量处于存活状态
System.out.println(demo.name);
// demo引用置为null,对象失去引用,后续会被GC回收,实例变量生命周期随之结束
demo = null;
}
}
静态变量:方法区中的长周期映射
静态变量是使用static修饰的变量,属于类级别,不属于任何对象实例。静态变量存储在方法区中,其生命周期和类的生命周期完全一致。
当类被加载时,JVM会解析类信息,静态变量在类加载的准备阶段完成内存分配和初始值设置,进入存活状态;只要类没有被卸载,静态变量就会一直存在于方法区中;当类被卸载时,方法区中对应的类信息包括静态变量才会被回收,静态变量生命周期结束。
类的卸载条件非常苛刻,一般需要满足三个条件:该类的所有实例都已经被回收、加载该类的类加载器已经被回收、该类对应的java.lang.Class对象没有任何地方被引用。因此在大多数情况下,静态变量的生命周期和整个应用程序的生命周期一致。
静态变量生命周期示例:
public class StaticVariableDemo {
// 静态变量count,存储在方法区,生命周期和类一致
private static int count = 0;
public StaticVariableDemo() {
count++;
}
public static void main(String[] args) {
// 类加载完成后,count就已经存在于方法区,处于存活状态
System.out.println(StaticVariableDemo.count);
new StaticVariableDemo();
new StaticVariableDemo();
System.out.println(StaticVariableDemo.count);
// 只要StaticVariableDemo类没有被卸载,count就会一直存在
}
}
变量生命周期映射的核心规则总结
| 变量类型 | 存储区域 | 生命周期起点 | 生命周期终点 |
|---|---|---|---|
| 局部变量 | 虚拟机栈(栈帧局部变量表) | 方法调用,栈帧创建 | 方法执行结束,栈帧出栈 |
| 实例变量 | 堆(对象实例内部) | 对象创建,new操作执行 | 对象被垃圾回收 |
| 静态变量 | 方法区 | 类加载,准备阶段完成 | 类被卸载 |
| 常量(final修饰的静态变量) | 方法区(常量池) | 类加载,准备阶段完成 | 类被卸载 |
常见误区说明
很多开发者会误以为局部变量中的对象引用指向的对象也存储在虚拟机栈中,实际上对象引用本身作为局部变量存储在栈的局部变量表,而引用指向的对象实例始终存储在堆中。局部变量销毁时,只是销毁了栈中的引用,堆中的对象只有在没有其他引用时才会被回收。
另外需要注意,基本数据类型的实例变量虽然是基本类型,但作为对象的一部分,也是和对象一起存储在堆中,而不是存储在栈里,这一点和局部变量中的基本数据类型存储位置有明显区别。