核心概念:一句话概括
- 栈:是线程私有的,生命周期与线程相同,它像一个“数据结构栈”(后进先出 LIFO),存储的是方法调用和基本数据类型变量,速度快,但空间小。
- 堆:是所有线程共享的一块大内存区域,它存储的是对象实例和数组,速度相对较慢,但空间大,是垃圾回收的主要区域。
深入理解:栈
栈是什么?
栈是 JVM 中为每个线程创建的私有的内存区域,你可以把它想象成一叠盘子,你最后放上去的盘子,会最先被拿走(这就是后进先出,LIFO - Last-In, First-Out)。

栈里存什么?
栈主要存储两样东西:
a) 栈帧 每当一个方法被调用时,JVM 会为该方法创建一个“栈帧”(Stack Frame),并将其压入栈中,当一个方法执行完毕,对应的栈帧就会从栈中弹出。 一个栈帧包含了:
- 局部变量表:存储方法中定义的基本数据类型(
int,double,boolean等)和对象的引用(Object reference),注意,它只存引用,不存对象本身。 - 操作数栈:像一个临时的计算区域,用于执行字节码指令时的数据计算和传递。
- 动态链接:指向运行时常量池的引用,用于解析方法调用。
- 方法返回地址:方法执行完成后,程序应该从哪里继续执行。
b) 基本数据类型变量
你在方法里写 int a = 10;,这个变量 a 和它的值 10 就直接存储在栈帧的局部变量表中。
栈的特点
- 线程私有:每个线程都有自己独立的栈,线程之间无法访问对方的栈数据,因此是线程安全的。
- 生命周期:与线程的生命周期绑定,线程创建时,栈被创建;线程结束时,栈被销毁。
- 速度快:内存分配和回收速度非常快,因为它只是简单的压栈和出栈操作,类似于数据结构中的栈。
- 空间较小:栈的内存大小是固定的(可以通过
-Xss参数设置),通常只有几兆,如果线程请求的栈深度过大(无限递归),就会抛出StackOverflowError。 - 数据共享性差:数据只在当前线程的方法调用链中可见。
深入理解:堆
堆是什么?
堆是 JVM 中最大的一块内存区域,是所有线程共享的,它就像一个大型的“对象仓库”,所有的对象实例和数组几乎都是在堆上分配内存的。

堆里存什么?
- 对象实例:通过
new关键字创建的所有对象都存储在堆中。new Person()。 - 数组:数组也是对象,所以也存储在堆中。
new int[10]。 - 成员变量:对象中的非静态成员变量也随对象一起存储在堆中。
堆的特点
- 所有线程共享:堆中的内存可以被所有线程访问,当多个线程同时访问同一个堆中的对象时,需要考虑同步问题(线程不安全)。
- 生命周期:堆的生命周期与 JVM 的生命周期相同,只要 JVM 还在运行,堆就存在。
- 速度较慢:堆的内存分配和回收比栈要慢得多,因为堆是动态分配的,需要寻找合适的内存空间,并且有复杂的垃圾回收机制在工作。
- 空间较大:堆是 JVM 内存中最大的一块区域,可以通过
-Xms(初始大小)和-Xmx(最大大小)参数来调整。 - 垃圾回收的主要区域:堆是垃圾收集器管理的主要区域,当一个对象不再被任何引用指向时,GC 就会回收它所占用的内存。
堆的内部结构(现代 JVM 中的优化)
为了提高内存分配和回收的效率,现代 JVM 的堆内部并不是一整块,而是分成了几个不同的区域,最常见的是分代模型:
- 新生代:
- Eden 区:新创建的对象首先在这里分配。
- Survivor 区 (S0, S1):经过一次 GC 后仍然存活的对象会被移到这里,两个 Survivor 区交替使用。
- 特点:大部分对象在新生代创建后很快就不再使用,GC 非常频繁,速度很快。
- 老年代:
- 特点:在新生代中存活了足够多次(达到“年龄”阈值)的对象会被“晋升”到老年代,老年代的 GC(如 Full GC)频率较低,但耗时较长。
- 元空间:
- 在 JDK 1.8 及之后,永久代被元空间取代,元空间并不在虚拟机中,而是直接使用本地内存。
- 作用:存储类的元数据信息(如类名、字段、方法信息、常量池等)。
- 优点:避免了永久代可能出现的
OutOfMemoryError,因为本地内存不受 JVM 最大堆大小的限制。
代码示例:直观感受
让我们通过一段代码,看看 int、Object 和它们的引用分别在哪里。
public class MemoryDemo {
public static void main(String[] args) {
// 1. main 方法被调用,一个栈帧被压入 main 线程的栈中。
// 2. a 是基本数据类型,变量 a 和值 10 直接存储在 main 方法的栈帧(局部变量表)中。
int a = 10;
// 3. myObject 是一个引用变量,它本身存储在 main 方法的栈帧(局部变量表)中。
// 它指向的对象(new Object())是存储在堆内存中的。
Object myObject = new Object();
// 4. 调用 method1,会为 method1 创建一个新的栈帧,并压入栈顶。
method1(a, myObject);
}
public static void method1(int paramA, Object paramObject) {
// 5. paramA 是基本数据类型,是 main 中 a 的一个副本,存储在 method1 的栈帧中。
// 修改 paramA 不会影响 main 中的 a。
paramA = 20;
// 6. paramObject 是一个引用变量,是 main 中 myObject 的一个副本(指向同一个堆对象)。
// 它存储在 method1 的栈帧中,通过 paramObject 修改堆中对象的内容,
// main 中的 myObject 也能看到变化,因为它们指向同一个对象。
paramObject = new Object(); // 这里只是让 paramObject 指向了一个新对象,不影响 main 中的 myObject
}
}
内存分布图解:
+-------------------------+ <-- JVM 启动
| Heap (堆) | <-- 所有线程共享
|-------------------------|
| new Object() (myObject指向) |
| new Object() (paramObject新指向) |
+-------------------------+
| Stack (栈) | <-- main 线程私有
|-------------------------| <-- 栈顶 (最新)
| method1 栈帧 |
| - paramA: 20 | <-- 基本类型值的副本
| - paramObject: 引用地址 | <-- 引用变量的副本
|-------------------------|
| main 栈帧 |
| - a: 10 | <-- 基本类型值
| - myObject: 引用地址 | <-- 引用变量
+-------------------------+
核心区别与总结
| 特性 | 栈 | 堆 |
|---|---|---|
| 目的 | 存储方法调用和局部变量 | 存储对象实例和数组 |
| 共享性 | 线程私有,线程安全 | 所有线程共享,非线程安全 |
| 生命周期 | 与线程相同 | 与 JVM 相同 |
| 速度 | 快 (压栈/出栈) | 慢 (动态分配, GC) |
| 大小 | 小 (固定, -Xss) |
大 (可调整, -Xms, -Xmx) |
| 异常 | StackOverflowError (栈溢出) |
OutOfMemoryError (OOM) |
| 基本数据类型、对象引用、方法调用 | 对象实例、数组、成员变量 | |
| 管理方式 | 编译器自动管理,无需 GC | 垃圾回收器 自动管理 |
常见面试题
Q1: String str = new String("hello"); 这行代码在内存中做了什么?

A:
- 在堆中,字符串常量池中检查是否存在
"hello"这个字符串,如果不存在,则创建一个"hello"对象放入池中。 - 在堆的非池区域(普通堆内存)中,再创建一个新的
String对象,并用池中的"hello"来初始化它。 - 在栈中,为
str这个局部变量分配空间,并将指向堆中那个新创建的String对象的引用赋值给str。
栈中一个引用,堆中两个对象(一个在池,一个不在池),这就是 new String() 和直接赋值的区别。
Q2: 什么时候会发生 StackOverflowError?什么时候会发生 OutOfMemoryError?
A:
-
StackOverflowError:- 原因:线程的栈深度超过了其分配的最大深度。
- 场景:最常见的场景是无限或过深的递归调用,因为每次递归调用都会在栈上压入一个新的栈帧,当递归无法终止时,栈最终会溢出。
- 例子:
public void infiniteRecursion() { infiniteRecursion(); // 无限递归 }
-
OutOfMemoryError:- 原因:JVM 没有足够的内存来为新的对象分配空间,并且垃圾回收器也无法释放出足够的内存。
- 场景:
- 堆内存溢出:创建了大量对象,且这些对象都存活(被引用),导致堆空间被耗尽,这是最常见的原因。
- 方法区/元空间溢出:加载了过多的类,导致元空间被占满。
- 本地内存溢出:使用了
NIO直接分配了本地内存(DirectByteBuffer),超出了物理内存限制。
希望这个详细的解释能帮助你彻底理解 Java 的堆和栈!
