【JVM】创建对象一定分配在堆里吗

张开发
2026/6/7 12:15:46 15 分钟阅读

分享文章

【JVM】创建对象一定分配在堆里吗
不一定。Java 对象通常分配在堆里但经过 JVM 优化后有些对象可能不会真正分配到堆中甚至可能不会真实创建对象。你这张图表达的是对象分配的大致判断流程new 一个对象 ↓ 是否逃逸 ↓ 未逃逸 → 可能栈上分配 → 栈内存 ↓ 逃逸 / 不能栈上分配 ↓ 是否能使用 TLAB ↓ 能 → TLAB 分配 → Eden 区中的线程私有小块 ↓ 不能 → 普通堆分配 → Eden / Old 区1. 默认理解对象是在堆中分配的我们平时说UserusernewUser();一般会说user这个对象分配在堆中。更准确地说UserusernewUser();里面有两个东西user 变量在栈帧的局部变量表中 new User() 对象通常在堆中所以常见理解是引用在栈上对象在堆里但是 JVM 后面做了优化所以不是绝对的。2. 逃逸分析判断对象会不会“跑出去”逃逸分析就是 JVM 判断一个对象的作用范围。比如publicvoidtest(){UserusernewUser();user.nameTom;System.out.println(user.name);}这个user只在test()方法内部使用方法执行完之后外面拿不到它。这种情况叫对象没有逃逸JVM 可能会认为既然这个对象不会被外部访问那就没必要一定放到堆里。3. 栈上分配未逃逸对象可能放到栈里如果对象没有逃逸JVM 可能会把它分配到当前方法的栈帧里。这样有一个好处方法执行完栈帧销毁对象也跟着销毁不用等 GC 回收减轻堆内存和 GC 压力。比如publicvoidtest(){PointpnewPoint(1,2);intsump.xp.y;}如果p没有逃逸JVM 可能优化成类似publicvoidtest(){intx1;inty2;intsumxy;}甚至连Point对象都不真正创建了。这叫标量替换也就是说对象被拆成几个普通变量来处理。4. 什么时候对象会逃逸情况一作为返回值返回publicUsercreateUser(){UserusernewUser();returnuser;}这里user被返回给外部方法了外面还能继续使用它所以它逃逸了。方法逃逸情况二赋值给成员变量publicclassTest{privateUseruser;publicvoidsetUser(){usernewUser();}}这个对象被保存到了成员变量中方法结束后对象还可能被使用所以逃逸了。情况三赋值给静态变量publicclassTest{publicstaticUseruser;publicvoidsetUser(){usernewUser();}}静态变量是类级别共享的其他线程也可能访问所以逃逸程度更高。线程逃逸情况四传给其他方法publicvoidtest(){UserusernewUser();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 中创建了对象UserusernewUser();这个过程不需要和其他线程抢 Eden 空间所以分配很快。但是如果之后这个对象被多个线程共享publicstaticUseruser;publicvoidinit(){usernewUser();}那么其他线程也能访问这个对象。这时候对象内部字段是否线程安全和它是不是在 TLAB 中分配没有关系。也就是说TLAB 只解决 new 对象时内存怎么分配的问题 不解决对象被多个线程访问时的数据安全问题11. 最终总结面试可以这样说Java 对象不一定一定分配在堆上。通常情况下new 出来的对象会分配在堆中优先在 Eden 区分配为了提高多线程分配效率JVM 会为每个线程在 Eden 区划分 TLAB对象优先在线程自己的 TLAB 中分配。但如果 JVM 通过逃逸分析发现对象没有逃逸出当前方法可能会进行栈上分配甚至通过标量替换消除对象分配。因此对象通常在堆中但不是绝对一定在堆中。更短一点对象通常分配在堆里 如果未逃逸可能栈上分配或被标量替换 如果进入堆分配优先尝试在线程自己的 TLAB 中分配 TLAB 属于 Eden 区所以本质仍然是堆内存。

更多文章