05_对象内存模型(堆 栈 方法区)
一、内存模型概述
Java 程序运行时,内存被划分为多个不同区域,各自承担特定职责。其中,堆(Heap)、虚拟机栈(Stack,简称 “栈”) 和方法区(Method Area) 是与对象创建、使用密切相关的核心区域。它们共同管理对象的生命周期:方法区存储类的结构信息,栈存储方法调用和局部变量,堆存储对象的实际数据。
核心作用:
- 确保对象的创建、使用和回收有序进行
- 隔离不同线程的数据(栈为线程私有,堆和方法区为线程共享)

二、堆(Heap)
2.1 定义与核心特点
堆是 Java 中最大的内存区域,用于存储对象实例和数组,是所有线程共享的内存区域。
核心特点:
- 线程共享:所有线程都可访问堆中的对象(需注意线程安全问题)。
- 动态分配:对象内存大小在运行时确定,无需提前声明,由 JVM 自动分配。
- 垃圾回收:堆是垃圾回收器的主要工作区域,不再被引用的对象会被回收释放内存。
- 无需连续空间:堆中的对象内存可以不连续,JVM 通过指针或列表管理空闲内存。
2.2 存储内容
堆中主要存储两类数据:
- 对象实例:通过new关键字创建的对象(如new Person()),包含对象的所有成员变量(属性)。
- 数组:所有数组(无论基本类型数组还是引用类型数组)的实际元素都存储在堆中(数组引用存储在栈中)。
2.3 示例解析
public class Student {
private String name; // 成员变量
private int age; // 成员变量
}
// 方法中创建对象
public class Test {
public static void main(String[] args) {
Student s = new Student(); // s是引用,存储在栈中;new Student()是对象实例,存储在堆中
int[] scores = new int[3]; // scores是引用,存储在栈中;数组元素存储在堆中
}
}
new Student()创建的对象实例存储在堆中,包含name和age两个成员变量(默认值为null和0)。
new int[3]创建的数组,其 3 个int元素(0,0,0)存储在堆中。
三、虚拟机栈(Stack,简称 “栈”)
3.1 定义与核心特点
虚拟机栈是线程私有的内存区域,与线程的生命周期一致,用于记录方法的调用过程。每个方法被调用时,JVM 会在栈中创建一个栈帧(Stack Frame),方法执行完毕后栈帧出栈。
核心特点:
- 线程私有:每个线程有独立的栈,线程间的栈数据不共享。
- 先进后出(FILO):最后被调用的方法(栈顶栈帧)最先执行完毕并出栈。
- 内存连续:栈的内存空间是连续的,由 JVM 自动分配和释放,无需手动管理。
- 大小有限:栈的容量远小于堆(通常为几 MB),若方法调用层级过深(如递归层数过多),会导致StackOverflowError。
3.2 栈帧的组成
每个栈帧包含以下核心部分:
- 局部变量表:存储方法的参数和局部变量(如int a、String s等)。
- 操作数栈:方法执行过程中临时数据的运算区域(如算术运算、对象调用等)。
- 方法返回地址:方法执行完毕后,返回调用者的位置信息。
3.3 存储内容
栈中主要存储两类数据:
- 基本类型的局部变量:如int、boolean、double等,直接存储值。
- 对象的引用(地址):引用类型变量(如Student s)存储的是对象在堆中的内存地址,而非对象本身。
3.4 示例解析
public class Calculator {
// 方法:计算两数之和
public int add(int a, int b) { // a、b是参数,存储在add方法的栈帧局部变量表中
int result = a + b; // result是局部变量,存储在add方法的栈帧局部变量表中
return result;
}
}
public class Test {
public static void main(String[] args) { // main方法的栈帧先入栈
Calculator calc = new Calculator(); // calc是引用,存储在main方法的栈帧局部变量表中;new Calculator()对象在堆中
int sum = calc.add(3, 5); // add方法的栈帧入栈(在main栈帧上方),sum是局部变量,存储在main栈帧中
}
}
方法调用栈帧变化:
- main方法被调用,main栈帧入栈(包含calc、sum等局部变量)。
- 调用add(3,5),add栈帧入栈(包含参数a=3、b=5和局部变量result)。
- add方法执行完毕,add栈帧出栈,返回结果8给main方法的sum。
- main方法执行完毕,main栈帧出栈,栈为空。

四、方法区(Method Area)
4.1 定义与核心特点
方法区是线程共享的内存区域,用于存储类的结构信息(类元数据)。它在 JVM 启动时创建,关闭时销毁,是类加载后数据的 “仓库”。
注意:JDK 8 及以后,方法区的实现为元空间(Metaspace),存储在本地内存中;JDK 7 及以前为 “永久代”,存储在 JVM 内存中。本文以通用概念描述,不区分版本差异。
核心特点:
- 线程共享:所有线程可访问方法区中的类信息。
- 存储持久:类信息在类加载后一直存在,直到 JVM 关闭(通常不参与垃圾回收)。
4.2 存储内容
方法区主要存储以下数据:
- 类的结构信息:类的名称、父类、接口、字段(属性)、方法的定义(如方法名、参数列表、返回值类型)。
- 常量池:包含字符串常量(如"hello")、基本类型常量(如123)、符号引用(如类和方法的全限定名)。
- 静态变量:类中被static修饰的变量(属于类,不属于对象)。
- 方法字节码:方法的编译后字节码(二进制指令)。
4.3 示例解析
public class Dog {
// 静态变量(存储在方法区)
public static String species = "犬科";
// 成员变量(定义存储在方法区,实例值存储在堆中)
private String name;
// 方法(字节码存储在方法区)
public void bark() {
System.out.println(name + "在叫");
}
}
// 类加载后,方法区存储的信息:
// 1. Dog类的结构:名称为"Dog",父类为"Object",包含字段"species"(static)、"name",包含方法"bark()"
// 2. 常量池:字符串常量"犬科"、"在叫"等
// 3. 静态变量species的值"犬科"
五、三个区域的关联:对象创建与使用流程
当通过new关键字创建对象时,堆、栈、方法区协同工作,流程如下:
- 类加载检查:JVM 先检查方法区中是否已加载该类的信息。若未加载,通过类加载器将类的信息加载到方法区。
- 堆中分配内存:在堆中为新对象分配内存,存储对象的成员变量(初始化为默认值)。
- 栈中存储引用:在栈的局部变量表中创建引用变量,存储对象在堆中的内存地址。
- 方法调用与栈帧:调用对象的方法时,JVM 在栈中创建方法的栈帧,执行方法字节码(从方法区获取)。
示例流程:
// 创建对象
Dog dog = new Dog();
- 步骤 1:检查方法区是否有Dog类信息,若没有则加载(存储类结构、方法字节码等)。
- 步骤 2:在堆中创建Dog对象,初始化name为null。
- 步骤 3:栈中dog变量存储堆中对象的地址(如0x001)。
- 步骤 4:调用dog.bark()时,栈中创建bark方法的栈帧,从方法区获取bark的字节码执行。

六、堆、栈、方法区的对比
| 区域 | 线程属性 | 核心存储内容 | 内存管理方式 | 大小 |
|---|---|---|---|---|
| 堆 | 共享 | 对象实例、数组元素 | 动态分配,垃圾回收器回收 | 最大(几 GB) |
| 虚拟机栈 | 私有 | 方法栈帧(局部变量、参数、返回地址) | 自动分配释放(方法调用 / 结束) | 较小(几 MB) |
| 方法区 | 共享 | 类元数据、常量池、静态变量、方法字节码 类 | 卸载时回收(极少发生) | 中等(几十 MB) |
七、常见问题与注意事项
- 对象与引用的区别:
- 对象是堆中的实际数据(如new Dog()),占内存较大。
- 引用是栈中的地址(如Dog dog),占内存较小(通常 8 字节)。
- 引用可以为null(表示不指向任何对象),但对象不能为null。
- 静态变量与成员变量的存储差异:
- 静态变量(static)存储在方法区,属于类,只有一份副本。
- 成员变量存储在堆中,每个对象有独立的副本。
- 字符串常量的存储:
- 直接赋值的字符串(如String s = "abc"),"abc"存储在方法区的常量池,s是栈中的引用。
- new String("abc")会在堆中创建对象,引用"abc"指向常量池的"abc"。
- 内存溢出场景:
- 堆溢出(OutOfMemoryError):创建过多对象且未回收(如无限循环new Object())。
- 栈溢出(StackOverflowError):方法调用层级过深(如无限递归)。
八、总结
堆、栈、方法区是 Java 内存模型的核心,各自承担不同职责:
- 堆是对象的 “栖息地”,存储对象实例和数组元素,是动态内存分配的核心。
- 栈是方法调用的 “记录仪”,通过栈帧记录方法执行过程,存储局部变量和对象引用。
- 方法区是类信息的 “数据库”,存储类结构、常量、静态变量和方法字节码。
理解三者的分工与关联,能帮助开发者更清晰地把握对象的生命周期,排查内存相关问题(如内存泄漏、溢出),是深入理解 Java 运行机制的基础。

浙公网安备 33010602011771号