【JVM】创建对象一定分配在堆里吗
2026/6/7 12:15:45 网站建设 项目流程

不一定。Java 对象通常分配在堆里,但经过 JVM 优化后,有些对象可能不会真正分配到堆中,甚至可能不会真实创建对象。

你这张图表达的是对象分配的大致判断流程:

new 一个对象 ↓ 是否逃逸? ↓ 未逃逸 → 可能栈上分配 → 栈内存 ↓ 逃逸 / 不能栈上分配 ↓ 是否能使用 TLAB? ↓ 能 → TLAB 分配 → Eden 区中的线程私有小块 ↓ 不能 → 普通堆分配 → Eden / Old 区

1. 默认理解:对象是在堆中分配的

我们平时说:

Useruser=newUser();

一般会说user这个对象分配在堆中。

更准确地说:

Useruser=newUser();

里面有两个东西:

user 变量:在栈帧的局部变量表中 new User() 对象:通常在堆中

所以常见理解是:

引用在栈上,对象在堆里

但是 JVM 后面做了优化,所以不是绝对的。


2. 逃逸分析:判断对象会不会“跑出去”

逃逸分析就是 JVM 判断一个对象的作用范围。

比如:

publicvoidtest(){Useruser=newUser();user.name="Tom";System.out.println(user.name);}

这个user只在test()方法内部使用,方法执行完之后,外面拿不到它。

这种情况叫:

对象没有逃逸

JVM 可能会认为:既然这个对象不会被外部访问,那就没必要一定放到堆里。


3. 栈上分配:未逃逸对象可能放到栈里

如果对象没有逃逸,JVM 可能会把它分配到当前方法的栈帧里。

这样有一个好处:

方法执行完,栈帧销毁,对象也跟着销毁

不用等 GC 回收,减轻堆内存和 GC 压力。

比如:

publicvoidtest(){Pointp=newPoint(1,2);intsum=p.x+p.y;}

如果p没有逃逸,JVM 可能优化成类似:

publicvoidtest(){intx=1;inty=2;intsum=x+y;}

甚至连Point对象都不真正创建了。

这叫:

标量替换

也就是说,对象被拆成几个普通变量来处理。


4. 什么时候对象会逃逸?

情况一:作为返回值返回

publicUsercreateUser(){Useruser=newUser();returnuser;}

这里user被返回给外部方法了,外面还能继续使用它,所以它逃逸了。

方法逃逸

情况二:赋值给成员变量

publicclassTest{privateUseruser;publicvoidsetUser(){user=newUser();}}

这个对象被保存到了成员变量中,方法结束后对象还可能被使用,所以逃逸了。


情况三:赋值给静态变量

publicclassTest{publicstaticUseruser;publicvoidsetUser(){user=newUser();}}

静态变量是类级别共享的,其他线程也可能访问,所以逃逸程度更高。

线程逃逸

情况四:传给其他方法

publicvoidtest(){Useruser=newUser();save(user);}

这里要看save(user)方法内部怎么用。

如果save()只是读一下,不保存、不返回,可能还不算真正逃逸。

但如果save()把它保存到成员变量、集合、静态变量里,那就逃逸了。


5. 如果不能栈上分配,就进入堆分配流程

当对象逃逸了,或者 JVM 判断不能优化时,对象通常就要分配到堆里。

堆里主要分为:

年轻代 - Eden 区 - Survivor 区 老年代 Old

大多数普通新对象会先进入 Eden 区。


6. TLAB 是什么?

TLAB 全称是:

Thread Local Allocation Buffer

意思是:

线程本地分配缓冲区

它不是一块新的内存区域,而是Eden 区中给每个线程提前划分出来的一小块专属空间

可以理解为:

Eden 区是一大片公共区域 TLAB 是每个线程在 Eden 中提前圈出来的小地盘

比如:

Eden 区 ┌─────────────────────────────┐ │ 线程A的TLAB │ 线程B的TLAB │ 公共区域 │ └─────────────────────────────┘

7. 为什么需要 TLAB?

多线程同时创建对象时,如果都去 Eden 区申请空间,就会有并发问题。

比如线程 A 和线程 B 同时执行:

newUser();

它们都要在堆中找一块空闲内存。

如果没有控制,可能两个线程都分配到了同一块内存位置,这肯定不行。

所以直接在公共堆中分配对象,需要加锁或者 CAS 控制,会影响性能。

TLAB 的作用就是:

每个线程先在自己的 TLAB 中分配对象 只要 TLAB 没满,就不需要和其他线程竞争

所以它提高的是:

对象内存分配的效率

8. TLAB 不是“栈内存”

这一点很容易混淆。

TLAB 虽然是线程私有的,但它仍然属于堆。

也就是说:

TLAB 内存 ∈ Eden 区 ∈ 堆

所以:

对象分配到 TLAB,本质上还是分配在堆里

它只是堆里的一块线程私有分配区域。


9. TLAB 和栈上分配的区别

对比栈上分配TLAB 分配
位置虚拟机栈的栈帧中堆的 Eden 区中
是否属于堆不属于堆属于堆
依赖条件对象未逃逸对象需要堆分配,且 TLAB 有空间
回收方式方法结束自动销毁由 GC 回收
目的减少堆分配和 GC提高堆内存分配速度

一句话区分:

栈上分配:对象可能不进堆 TLAB 分配:对象进堆,但在线程私有的小块堆空间中快速分配

10. TLAB 只保证“分配过程”线程安全

你笔记里这句话很关键:

TLAB 的线程安全仅体现在内存分配过程中,与对象本身的线程安全性无关。

什么意思?

比如线程 A 在自己的 TLAB 中创建了对象:

Useruser=newUser();

这个过程不需要和其他线程抢 Eden 空间,所以分配很快。

但是如果之后这个对象被多个线程共享:

publicstaticUseruser;publicvoidinit(){user=newUser();}

那么其他线程也能访问这个对象。

这时候对象内部字段是否线程安全,和它是不是在 TLAB 中分配没有关系。

也就是说:

TLAB 只解决 new 对象时内存怎么分配的问题 不解决对象被多个线程访问时的数据安全问题

11. 最终总结

面试可以这样说:

Java 对象不一定一定分配在堆上。通常情况下,new 出来的对象会分配在堆中,优先在 Eden 区分配;为了提高多线程分配效率,JVM 会为每个线程在 Eden 区划分 TLAB,对象优先在线程自己的 TLAB 中分配。但如果 JVM 通过逃逸分析发现对象没有逃逸出当前方法,可能会进行栈上分配,甚至通过标量替换消除对象分配。因此,对象通常在堆中,但不是绝对一定在堆中。

更短一点:

对象通常分配在堆里; 如果未逃逸,可能栈上分配或被标量替换; 如果进入堆分配,优先尝试在线程自己的 TLAB 中分配; TLAB 属于 Eden 区,所以本质仍然是堆内存。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询