Java中对象实例存于堆,引用变量存于栈;方法调用生成栈帧,线程私有;堆线程共享,需考虑线程安全;栈溢出多因递归过深,堆溢出多因对象过多或内存泄漏。

Java中类与对象的内存分配,核心就落在堆与栈的分工上:类本身不占堆或栈,但它的实例(对象)一定在堆里;而创建和使用这个对象时涉及的变量、方法调用、参数等,大多落在栈上。
对象本体在堆,引用变量在栈
当你写 new Person(),JVM会在堆中开辟一块内存存放这个 Person 对象的所有字段(比如 name、age),而代码中声明的变量,例如 Person p = new Person();,这里的 p 本身是个引用变量,它存储的是堆中那个对象的地址值——这个 p 变量(包括它的类型、地址值)就存在当前方法的栈帧里。
也就是说:
- 堆存的是“东西本身”(对象、数组、内容数据)
- 栈存的是“谁在用、怎么用、用到哪一步”(变量名、地址、方法参数、返回地址、局部计算过程)
方法执行靠栈,生命周期由栈帧控制
每次调用一个方法,JVM 就为它生成一个栈帧压入当前线程的栈。这个栈帧里有局部变量表(存 int、boolean、p 这样的引用)、操作数栈(字节码执行的临时空间)、动态链接(指向常量池的方法入口)等。
立即学习“Java免费学习笔记(深入)”;
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
方法一结束,整个栈帧立刻出栈销毁——里面所有局部变量(包括引用变量 p)同时失效。但堆里的对象不会立即消失,只要还有其他引用(比如被放进 HashMap 或作为成员变量存在另一个对象里),它就能继续活在堆中。
线程私有 vs 全局共享,直接决定线程安全问题
每个线程都有自己独立的栈,所以你在方法里定义的 int count = 0 或 String s = "hello",别的线程完全看不到;但如果你把 new ArrayList() 赋值给一个静态变量或传给另一个线程,大家操作的就是堆里同一个对象——这时候不加同步,就容易出现并发修改异常或数据错乱。
这也是为什么集合类的线程安全设计(如 ConcurrentHashMap、Vector)本质是在堆对象的操作逻辑上加锁或分段,而不是去动栈。
错误现象能帮你快速定位问题区域
遇到 StackOverflowError?基本是栈撑爆了:递归太深、方法嵌套太多、单个方法里定义了超大数组(注意:数组本身是对象,在堆里;但定义 int[] arr = new int[1000000] 的语句中,arr 引用在栈,new 出的数组在堆;不过如果写成 int[] arr = new int[1000000] 在局部且栈帧过大,也可能间接诱发栈溢出)。
遇到 OutOfMemoryError: Java heap space?说明堆扛不住了:对象创建太多没及时回收、缓存没设上限、一次加载超大文件到内存等。

















