1.生命周期不一样
栈:跟方法走
堆:生命周期不确定
- 栈里的数据 :跟着方法走。方法调用 → 压入栈帧;方法执行完毕 → 栈帧直接弹出销毁。生命周期很短,不需要垃圾回收。 存:局部变量、基本类型、对象引用地址(不是对象本身)
- 堆里的对象:生命周期不确定。对象创建后,可能跨很多方法、跨线程使用,方法跑完对象不一定没用。什么时候没人引用了,才由 GC 回收。
2.访问速度差距大
栈:分配和回收极快,内存连续,分配方式简单(移动栈指针),接近 CPU 寄存器速度。
堆:分配慢很多,需要在一大片空闲内存里找一块合适空间,还要维护空闲链表、GC 标记整理。
3.隔离并发,天然保证线程安全
- 栈:线程私有,每个线程有自己独立栈,不存在多线程同时修改同一个栈内局部变量,天然无竞争。
- 堆:线程共享,所有线程都能访问里面的对象,需要同步锁保证安全。
4. 内存大小特性不同
- 栈空间很小(一般几 MB),适合存放少量局部变量、引用(栈深度有限,调用太深会
StackOverflowError) - 堆空间很大(G 级别),适合存放数量不确定、大小不确定的对象、集合
补充一个高频误区
基本类型一定在栈,对象一定在堆❌
局部变量里的基本类型在栈;成员变量跟着对象走,对象在堆,成员变量就在堆。✅
还有 JDK 逃逸分析:如果对象不逃出方法,JIT 可以做栈上分配,对象直接分配在栈,避免 GC 开销,这是优化,不是默认行为。
即:
逃逸分析 = JIT 检查:这个 new 出来的对象,会不会跑到方法外面被别的代码拿到。如果它 "逃不出去",就叫【不逃逸】,JIT 可以优化,不在堆上创建对象,改在栈上分配。
局部变量:写在方法内 / 代码块({})里面的变量,只在当前方法生效
基本类型 :Java 原生基础类型,不是对象,没有 new,存的就是真实值 8 个: byte、short、int、long float、double char boolean
区分:
- 基本类型:存值本身
- 引用类型(类、数组、String、集合等):存对象地址
成员变量:写在类里面、方法外面的变量,属于类 / 对象(实例变量 + 静态变量)
例如代码:
java
public void test() {
User u = new User();
u.age = 18;
System.out.println(u.age);
}

逃逸分析判断:这个对象有没有逃逸?
逃逸 = 对象的引用流出当前方法,外面别的代码 / 别的线程可以拿到这个对象

