欢迎光临
我们一直在努力

【java】小白也能看懂的堆栈讲解

一文彻底搞懂“堆”和“栈”


在 Java 面试中,几乎一定会被问到:

  • 什么是堆?什么是栈?
  • new 出来的对象放在哪?
  • 局部变量放在哪?
  • 为什么会 StackOverflowError?
  • 为什么会 OOM?
  • 方法调用过程是怎样的?

简单来说:

“对象在堆里,变量在栈里。”

这篇文章,我们从底层讲清楚:

  • 什么是栈?
  • 什么是堆?
  • 为什么必须分成两块?
  • 它们的设计意义是什么?
  • JVM 是如何管理的?
  • 面试如何回答才显得有深度?

一、先从一个问题开始

思考:

public void test() {
int a = 10;
User user = new User();
}

问题:

  • a 存在哪?
  • user 存在哪?
  • new User() 存在哪?
  • 方法调用结束后会发生什么?

要回答这些问题,就必须理解堆和栈。


二、什么是栈(Stack)?

1️⃣ 栈的本质

栈是一种:

先进后出(LIFO)的数据结构

想象一个盘子堆:

  • 只能从上面放
  • 只能从上面拿

2️⃣ 在 JVM 中的栈

更准确地说:

JVM 中叫做“虚拟机栈(Java Virtual Machine Stack)”

每个线程都有自己的栈。


3️⃣ 栈中存什么?

栈中存储的是:

栈帧(Stack Frame)

每调用一个方法,就会创建一个栈帧。

栈帧中包含:

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 方法出口信息

4️⃣ 方法调用过程

main() {
test();
}

执行流程:

  • main 入栈
  • test 入栈
  • test 执行完出栈
  • main 继续执行
  • 这就是为什么递归过深会:

    StackOverflowError

    因为栈空间是有限的。


    三、什么是堆(Heap)?

    堆是:

    用于存储对象实例的内存区域


    1️⃣ 堆的特点

    • 线程共享
    • 空间大
    • 存储对象
    • 由 GC 管理

    2️⃣ new 出来的对象在哪里?

    User user = new User();

    过程:

  • 在堆中创建 User 对象
  • 在栈中创建引用变量 user
  • user 指向堆中的对象
  • 图示(逻辑):

    栈:
    user —–> 堆:
    User对象


    四、为什么必须分成堆和栈?

    这是一个设计哲学问题。

    如果不分:

    • 方法调用如何高效管理?
    • 变量生命周期如何控制?
    • 垃圾回收如何优化?

    栈的设计目的

    • 高效
    • 自动释放
    • 生命周期明确

    方法结束:

    栈帧自动销毁

    不需要 GC。


    堆的设计目的

    • 动态分配
    • 支持对象共享
    • 生命周期不确定

    必须依赖:

    垃圾回收器(GC)


    五、深入 JVM 视角

    JVM 内存结构包括:

    • 程序计数器
    • 虚拟机栈
    • 本地方法栈
    • 方法区(元空间)

    栈的特点

    • 每个线程私有
    • 不存在垃圾回收
    • 容易发生 StackOverflowError

    堆的特点

    • 线程共享
    • 存在垃圾回收
    • 容易发生 OOM

    六、堆的内部结构(进阶)

    现代 JVM 堆分为:

    • 新生代
      • Eden
      • Survivor
    • 老年代
    • 元空间(JDK8后)

    对象创建流程:

  • 优先分配在 Eden
  • 多次 GC 后进入老年代
  • 老年代满 → Full GC

  • 七、面试高频问题解析


    1️⃣ 局部变量一定在栈吗?

    不一定。

    例如:

    User user = new User();

    • user 在栈
    • User 对象在堆

    但:

    JIT 可能会进行:

    逃逸分析 + 栈上分配

    小对象可能直接分配在栈。


    2️⃣ 为什么会 StackOverflowError?

    常见原因:

    • 递归过深
    • 栈空间太小

    可以通过:

    -Xss

    调整。


    3️⃣ 为什么会 OOM(Out Of Memory,内存溢出/内存不足)?

    • 堆空间不足
    • 内存泄漏
    • 大对象频繁创建

    通过:

    调大内存:增加 JVM 堆大小(-Xmx)或系统物理内存。
    优化代码:修复内存泄漏(如未释放资源、集合对象无限增长)、减少大对象创建。

    调整。


    4️⃣ String 在哪?

    • 字符串对象在堆
    • 字符串常量池在堆(JDK8+)

    5️⃣ 基本类型一定在栈?

    如果是:

    • 局部变量(方法、构造器或代码块内部) → 栈

    如果是:

    • 成员变量(类中,方法外) → 在堆对象中

    八、常见误区


    ❌ 误区 1:对象一定在堆

    不一定(逃逸分析)。


    ❌ 误区 2:栈比堆快

    不完全准确。

    栈快的原因:

    • 内存连续
    • 自动回收
    • 无 GC

    ❌ 误区 3:栈存基本类型,堆存对象

    成员变量中的基本类型:

    也是在堆中。


    九、为什么堆栈如此重要?

    因为它决定:

    • 性能
    • GC 行为
    • 内存泄漏
    • 线程安全
    • 并发问题

    理解堆栈,本质上是理解:

    Java 内存模型的基础。


    十、一个高质量面试回答示例

    如果面试官问:

    请你讲一下堆和栈的区别?

    可以这样回答:

  • 栈是线程私有,用于方法调用
  • 堆是线程共享,用于对象存储
  • 栈自动释放,不参与 GC
  • 堆需要 GC 管理
  • 栈可能 StackOverflowError
  • 堆可能 OOM
  • JIT 可能优化为栈上分配
  • 这就从“基础”讲到了“优化”。


    十一、总结

    堆和栈不仅仅是:

    “内存的两个区域”

    它代表了:

    • 生命周期管理策略
    • 性能与安全的平衡
    • JVM 设计思想
    • 垃圾回收机制的基础
    赞(0)
    未经允许不得转载:171主机测评 » 【java】小白也能看懂的堆栈讲解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址