HotSpot虚拟机对象探秘:对象的创建,我居然看懂了!

88 阅读6分钟



大家好,我是你们的老朋友小米,一个每天都在代码与咖啡之间切换状态的 31 岁程序员。

今天这篇文章,我想带你一起走进 Java 世界中一个特别“魔幻”的角落——对象创建。

这个话题你肯定不陌生,对吧?我们平时写代码:

就这一句,貌似平平无奇,但你有没有想过,JVM 背后都悄咪咪做了些什么?对象是怎么一步一步,从“无中生有”被变出来的?HotSpot 是怎么分配内存、初始化、搞定 header 的?

今天,小米就带你走进 HotSpot 的幕后舞台,一探对象创建的秘密。

一切从 new 开始,但 new 只是个“开关”

那天我正和同事阿刚喝着下午茶,聊着最近项目优化的事,他突然抛来一句:

“你知道 JVM 是怎么创建一个 Java 对象的吗?”

当时我一愣,心里想着不就 new 一下嘛,还能有啥。

但等他掏出一张纸开始画图的时候,我才知道,我懂得还远远不够。

其实当我们写下 new User() 这句代码时,Java 编译器做的只是把它编译成了一条字节码指令:new。

而真正的魔术,是在 HotSpot 虚拟机执行字节码时才开始的。

这就好比你按下了电梯按钮,电梯是不是真的来,还得看调度系统是不是给你安排得当。

HotSpot对象创建的五个阶段,就像造房子

阿刚给我讲了一个特别形象的比喻,他说 JVM 创建对象就像造房子,大致可以拆解为这五个步骤:

  • 类加载检查(确定图纸是否有)
  • 内存分配(拿地皮)
  • 内存初始化(打地基)
  • 设置对象头(装修主体)
  • 执行构造方法(软装上线)

我当时猛点头,这不就是咱写代码时那句 new 背后的隐藏剧本吗!

下面我就一个个来讲,保证你听完之后也能在面试官面前吹爆 JVM 对象创建流程。

第一步:类加载检查,图纸有没有?

每次 new 一个对象,JVM 的第一反应是:这个类我加载过没?

HotSpot 会通过类加载子系统去检查类的元数据是否已经加载、初始化过。

如果没有?那就先通过双亲委派模型把这个类加载进来。别忘了,类的元信息是在 方法区(或说元空间 Metaspace) 里的。

没有图纸,房子肯定不能造,对吧。

第二步:内存分配,拿地皮

这个阶段,HotSpot 要为新对象划一块地盘,也就是在堆内存中“圈一块地”。

它有两种分配方式,分别叫做:

  • 指针碰撞(bump the pointer)
  • 空闲列表(free list)

如果堆内存是规整的,也就是已经整理过、不会有内存碎片,JVM 就直接通过一个叫 alloc_ptr 的指针往前一推,一口气分配下一块内存,效率超高。

如果堆是不规整的(比如用了 CMS 垃圾收集器),那 JVM 就得在内存中找一个合适的空闲块,效率会稍慢。

但这还不够,JVM 还得保证线程安全——毕竟多线程下好几个线程都可能在同时 new 对象啊。

于是,它用上了三板斧:

  • 通过加锁(如 TLAB 锁)
  • 通过原子指令 CAS + 重试
  • 使用线程本地分配缓冲(TLAB)

大多数时候,我们的对象是在 TLAB(Thread Local Allocation Buffer) 中分配的,线程独占,不用加锁,效率拉满。

第三步:内存初始化,打地基

拿到了地皮之后,HotSpot 下一步就是“清地基”——也就是把刚分配的内存区域清零。

这一步很关键,如果不清零,那对象的字段可能全是垃圾值,后面出 bug 可就一发不可收拾了。

清零完毕后,JVM 知道这块内存已经是“干净的”,可以用来创建对象了。

第四步:设置对象头,装修主体

HotSpot 中的每个对象,都不是裸奔的,它的前面都带着一个“对象头”。

这个对象头包含了两类信息:

  • Mark Word(标记字段) :用于存储对象的哈希码、GC 分代年龄、锁信息等等。
  • Klass Pointer:指向对象所属的类的元数据。

JVM 会把这些信息填充进去,就像把房子主体的水电管线都布好。

这一步结束,对象就有了“身份”和“结构”。

第五步:执行构造方法,软装上线

最后一步才是我们 Java 程序员最熟悉的一步:

JVM 会根据类的构造函数,逐一执行初始化代码,比如字段赋值、初始化列表等。

这一步,就是给房子装上窗帘、沙发、灯具——真正成为一个“能住人”的家。

“new”背后的思考:为什么你要懂这些?

听完阿刚那天的分享,我感触特别深:

我们平时太容易把对象创建当成“理所当然”,却没意识到背后是一套精密的设计和优化。

尤其在高并发、内存敏感的业务场景下,理解对象创建过程能帮你:

  • 识别性能瓶颈(比如频繁 GC 的根源可能是对象创建太多)
  • 更高效地使用 TLAB 与逃逸分析
  • 编写更贴合 JVM 优化路径的代码

比如:用对象池复用对象、避免创建临时对象、使用基本类型替代包装类型等等。

对象创建的“高级玩法”:逃逸分析与栈上分配

其实 HotSpot 还会进一步优化对象的创建,比如当 JVM 发现某个对象 没有逃出当前方法 时,它甚至会直接在栈上分配对象,而不是堆中。

这叫做:逃逸分析(Escape Analysis)

如果逃逸分析判断对象“只在方法内部用”,那它就不用堆内存了,直接在栈中分配,效率更高,GC 也不会管它。

你想啊,这对象一用完方法就销毁了,压根不用等 GC。

还有一种优化方式叫做:标量替换,JVM 会把整个对象拆成若干个字段来优化分配,用得更灵活。

这些优化,都得益于你理解对象是怎么创建的,才能在实际项目中利用起来。

总结

说到这,你会发现,对象的创建远比你想象中复杂,但每一个步骤都是为了性能、并发、安全做出妥协和平衡。

让我再用建房子的比喻总结一次对象创建的五大步骤:

  • 看图纸(类加载检查)
  • 拿地皮(内存分配)
  • 打地基(内存清零)
  • 装修主体(设置对象头)
  • 上软装(执行构造函数)

如果你能理解这些流程,相信在调优性能、理解内存问题、写出更高质量代码时,你会有意想不到的收获。

最后,给你一个小问题

在 JVM 中,如果你创建了 100 万个小对象,它们都短生命周期、只在方法里用,你会如何优化它?

欢迎在评论区留言,我们一起讨论 JVM 的高级优化姿势!

END

今天就聊到这啦,别忘了给小米点个 【在看】【关注】 ,我会继续分享更多底层技术和实战技巧~

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!