← 每日面试题 学习看板

说下jvm对象的创建过程

面试题/真实面试/2021/说下jvm对象的创建过程.md

说下jvm对象的创建过程

首次来源:京东 2021 面试部门零售

原题

说下jvm对象的创建过程

完整讲解(权威版)

最后更新:2026-10-30 · 基于第 1 次作答

L1 · 30 秒速记

结论:对象创建 = 类加载检查 → 分配内存 → 零值初始化 → 对象头 → 构造方法,并发分配靠 TLAB/CAS。 关键词:类加载检查 · 指针碰撞 · TLAB · 对象头 · 逃逸分析 边界:栈上分配(逃逸分析+标量替换)是优化,不是所有对象都进堆。

L2 · 核心讲解

核心考点定位:面试官想听的是 new 一条指令背后 JVM 做了什么,重点是"分配内存的并发处理"和"对象内存布局"。

机制链(≥3 环):

  1. 类加载检查 → new 时先检查常量池能否定位到类的符号引用,类未加载则先加载/解析/初始化。
  2. 分配内存 → 类加载检查通过后分配对象所需内存(大小在类加载时确定);指针碰撞(连续)或空闲列表(碎片)。
  3. 并发分配 → 多线程并发 new 需保证原子性:TLAB(线程本地分配缓冲)优先,不足时用 CAS + 失败重试。
  4. 初始化 → 分配后零值初始化(保证字段默认值)、设置对象头(Mark Word + 类型指针)、执行构造方法。
  5. 栈上分配 → 逃逸分析 + 标量替换通过时,对象不逃逸可整体拆成标量在栈上分配,免堆分配与 GC。

证据锚点:

对比与权衡:

分配方式前提并发处理适用
指针碰撞堆规整CAS + 重试 / TLABSerial、ParNew
空闲列表堆碎片需维护空闲链表CMS
栈上分配逃逸分析 + 标量替换无(线程栈)非逃逸小对象

常见误区:

  1. 错误:对象创建第一步就是分配内存 → 正确:先做类加载检查 → 为什么:类未加载无法确定对象大小。
  2. 错误:所有对象都在堆 → 正确:逃逸分析通过可栈上分配(标量替换)→ 为什么:这是 JIT 优化。
  3. 错误:new 后对象字段是随机值 → 正确:分配后先零值初始化再执行构造 → 为什么:保证默认值语义。
  4. 错误:对象分配都要全局锁 → 正确:TLAB 线程本地分配免锁 → 为什么:多数 new 在 TLAB 内完成。
  5. 错误:对象头只有类型信息 → 正确:Mark Word 还存哈希、分代年龄、锁标志 → 为什么:锁升级靠它记录状态。
  6. 错误:TLAB 满了直接失败 → 正确:TLAB 满先申请新 TLAB,仍不足才走全局 CAS 分配 → 为什么:保证大部分分配免锁。

适用边界:大对象(超过 TLAB)直接进老年代;对象创建流程对 TLAB 满、内存不足(OOM)时的处理需单独展开。

延伸追问(≥3)

  1. TLAB 是什么?解决了什么问题?
  1. 对象头里有什么?
  1. 什么是逃逸分析?

面试者参考回答(400–700 字)

【结论】 面试官您好,JVM 创建对象的核心流程是"类加载检查 → 分配内存 → 零值初始化 → 设置对象头 → 执行构造方法"五步,其中分配内存的并发处理是重点。

【三点】 第一,new 指令先检查常量池能否定位到类的符号引用,类未加载就先走加载、解析、初始化;类加载后对象大小才确定,才能分配内存。第二,分配内存要看堆是否规整——规整用指针碰撞(指针上移),不规整用空闲列表;而且多线程并发 new 必须保证原子性,JVM 用 TLAB(线程本地分配缓冲) 让每个线程先在 Eden 预分配一块区域,new 直接在 TLAB 分配,避免全局锁竞争,TLAB 不够再走 CAS + 失败重试。第三,内存分配后先做零值初始化(保证字段有默认值),再设置对象头(Mark Word 存哈希/分代年龄/锁状态 + 类型指针指向类元数据),最后执行构造方法。另外如果开启了逃逸分析且对象不逃逸方法外、能标量替换,对象可能直接栈上分配,不涉及堆分配和 GC。

【边界】 当然,不是所有对象都走这套堆分配——逃逸分析通过的对象会栈上分配,这是 JIT 优化;而且大对象会直接进老年代(TLAB 放不下)。这套流程在 TLAB 耗尽、堆内存不足(OOM)时还有额外的分配失败处理,如果面试官追问分配失败路径,要单独展开。

面试记录 2026-10-30(第 1 次)

题目:说下jvm对象的创建过程

难度:medium

标签:real-interview, jvm/oom, company/京东

评分:⭐⭐⭐☆☆(3/5)

我的回答

创建对象要考虑当前的情况,对象是否要跳出当前的栈。如果不需要,它当前对象是否是由开启了,当前的 JVM 是否开启了标量替换,并且当前的对象是否是只在这栈上的分配,不会涉及逃逸。如果是这样的话,它直接申请空间,把那个对象转化为对应的基础对象,基础类型的数据,而不是对象了,直接就赋值了。这时候它的分配整个直接在栈上分配,并不涉及后续的堆分配啊、堆回收之类的事。如果这个对象是一个引用对象,比如说 string 或者是一些哈希 map,这时候它会涉及申请堆空间,建立指针指向对应的堆空间。标准过程是从 new 开始,new 命令开始,然后检查类的信息是否加载到 JVM 中。如果未加载就去加载,如果已加载就开始通过类的信息去申请内存空间。判断当前的对象是要栈上分配还是要堆上分配,根据具体的引用情况去考虑。然后创建对象头,根据对象头信息去申请堆的空间。然后创建引用,把对象头的引用信息指向对应的堆空间。

点评

优点:逃逸分析 / 标量替换 / 栈上分配是亮点;new → 类加载检查 → 分配内存 → 对象头流程清楚;区分了栈分配与堆分配。

不足:漏了TLAB / 指针碰撞 / 空闲列表(分配内存的并发处理)、零值初始化、对象头的 Mark Word 布局;流程只到"对象头",没到构造方法。

补充:标准流程"类加载检查→分配内存(TLAB/CAS)→零值→对象头→构造",对象头 Mark Word 与锁升级相关。

完整讲解见上方「完整讲解(权威版)」