news 2026/10/5 1:59:31

new Object() 到底做了啥?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
new Object() 到底做了啥?

全文目录:

    • 开篇语
      • 0. 前言:那个让人心潮澎湃的 `new` 关键字!
      • 1. Java 对象:从“胚胎”到“呱呱坠地”的曲折过程
        • 阶段一:类加载检查 (Class Loading Check)
        • 阶段二:分配内存 (Allocate Memory)
        • 阶段三:初始化零值 (Zero Initialize)
        • 阶段四:设置对象头 (Set Object Header)
        • 阶段五:执行 `<init>` 方法 (Execute Constructor)
        • 整个流程总结一下:
      • 2. 对象头结构:你对象的“身份证”和“百宝箱”
        • 2.1 Mark Word (标记字段)
        • 2.2 Klass Pointer (类型指针)
        • 2.3 数组长度 (Array Length)
        • 2.4 对象内存布局示意图(64位 JVM + 指针压缩)
      • 3. 实战:自己动手看看对象头!
      • 4. 写在最后:知其然,更要知其所以然!
    • 文末

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

0. 前言:那个让人心潮澎湃的new关键字!

在 Java 里,万物皆对象(除了基本类型,但它们也有包装类)。而创造对象,靠的就是那个简单到几乎可以忽略的new关键字。

MyClass myObj = new MyClass();

有没有觉得这句话就像电影里的**“芝麻开门!”?🚪 只要你一喊,JVM 就忙活开了,帮你变出一个活生生的对象。但这个“变”的过程,可不是简简单单地“啪”一下就完成了。它是一个精密的、多阶段的、充满细节的流程**!

而且,你知道你new出来的每一个对象,除了你自定义的那些成员变量,JVM 还偷偷给它加了个**“户口本”吗?这个“户口本”就是神秘的对象头(Object Header)**!

今天,咱们就一步一个脚印,把这个“出生”和“户口本”的秘密彻底扒干净!🕵️‍♂️

1. Java 对象:从“胚胎”到“呱呱坠地”的曲折过程

咱们说的这个“对象创建”,实际上是指当 JVM 遇到new指令时,从那一刻到对象可以被使用之前的一系列动作。它大致可以分为以下几个步骤:

阶段一:类加载检查 (Class Loading Check)

“喂!你说的这个MyClass是个什么鬼?我认识它吗?” 🤨
JVM 首先会去运行时常量池找这个类的符号引用。如果找不到,或者发现这个类还没被加载、解析、初始化过,那就得先乖乖地把这个类加载进来!
这就像你要生娃,得先看看你有没有结婚证、准生证!要是没有,那就先去把证件办齐了!📝

阶段二:分配内存 (Allocate Memory)

“行,知道MyClass是个好东西了!那它得住哪儿啊?” 🏠
这一步就是为新对象分配内存空间。对象所需的内存大小在类加载完成后就确定了。主要在**堆(Heap)**上分配。
分配内存的方式有两种,这取决于你的 JVM 是怎么玩的:

  1. 指针碰撞 (Pointer Bump):如果 Java 堆是规整的(即所有用过的内存都放在一边,空闲内存放在另一边),那么只需要一个指针不断地往空闲内存方向移动,挪出与对象大小相等的空间即可。快得飞起!💨
  2. 空闲列表 (Free List):如果 Java 堆是不规整的(比如经过了标记-清除算法,内存碎片化严重),那就需要维护一个列表,记录哪些内存块是可用的。分配时从列表中找一块足够大的,然后更新列表。慢,但没办法!🐌

并发问题?多个线程同时new对象,都想抢同一块内存怎么办?
JVM 也想到了!通常有两种方案:

  • CAS (Compare And Swap):通过 CAS + 重试来保证原子性。咱上一篇文章刚聊过,这玩意儿大家都很熟了!😏
  • TLAB (Thread Local Allocation Buffer):推荐这种!😈 每个线程在 Java 堆中预先分配一小块自己的专属内存空间,就叫 TLAB。线程在 TLAB 里分配内存就不用同步了,只有 TLAB 用完了,才需要申请新的 TLAB,这时候才需要加锁。这就像是公司给每个员工发了小金库,大家在自己金库里花钱随便花,没钱了才去大金库领。效率高到爆!🚀
阶段三:初始化零值 (Zero Initialize)

“房子有了,但是里面都是毛坯房啊!总得有点默认配置吧?” 🛋️
内存分配完成后,JVM 会把分配到的内存空间(不包括对象头)都初始化为零值(null、0、false)。
这么做的好处是,即使你的代码里没有给成员变量赋初值,它们也能直接使用,得到一个默认的零值。这可比 C/C++ 里的随机值安全多了!👍

阶段四:设置对象头 (Set Object Header)

“嗯,毛坯房有了,那得给业主发个身份证、门牌号啥的吧?” 🏷️
这一步就是把新对象的对象头信息填充进去。这可是个重要的步骤!后面咱们会重点聊。它包括了这个对象的哈希码、GC 分代年龄、锁状态、指向类元数据的指针等等。这是对象的“户口本”!

阶段五:执行<init>方法 (Execute Constructor)

“好了!现在有了地址,有了身份证,终于可以装修入住,开始‘新生活’了!” 🎉
执行对象的构造函数(<init>方法),按照程序员的意愿对对象进行初始化。成员变量赋初始值、执行业务逻辑等等。
到这儿,一个活生生的、可以被程序使用的对象,就正式“呱呱坠地”了!👶

整个流程总结一下:
  1. 查户口:类加载检查
  2. 分地盘:分配内存 (指针碰撞/空闲列表,TLAB/CAS 搞定并发)
  3. 清零房:初始化零值
  4. 发证件:设置对象头
  5. 搞装修:执行构造方法

是不是感觉这new关键字背后,比你想象中复杂多了?😉

2. 对象头结构:你对象的“身份证”和“百宝箱”

现在咱们来揭秘那个神秘的对象头(Object Header)!它可不是一个简单的东西,它是对象在内存中的元数据,是 JVM 用来管理对象的关键信息。

对象头通常分为两部分:Mark Word (标记字段)和Klass Pointer (类型指针)。如果对象是数组,还会有一个数组长度字段。

2.1 Mark Word (标记字段)

这是对象头的核心!也是最让人眼花缭乱的部分。它存储了对象运行时的各种状态信息。
在 64 位 JVM 中,Mark Word 占用 8 字节(64 位)。但在 32 位 JVM 中,它只占用 4 字节。它的结构是动态的,会根据对象的状态变化而变化!这就像一个变色龙!🦎

Mark Word 通常包含以下信息(但不限于):

  • 哈希码 (HashCode):当对象第一次调用hashCode()方法时,这个哈希码就会被计算并存储在这里。

  • GC 分代年龄 (Age):对象在 Minor GC 中幸存的次数。每经历一次 Minor GC 且幸存,年龄就加 1。当年龄达到某个阈值(通常 15),就会晋升到老年代。

  • 锁标志位 (Lock Bits):这几个位是 Mark Word 中最重要的,它们定义了对象处于什么锁状态(无锁、偏向锁、轻量级锁、重量级锁、GC 标记)。

    • 无锁状态 (Normal):最原始的状态。
    • 偏向锁 (Biased Locking):针对只有一个线程访问的场景,避免 CAS 操作,效率最高。
    • 轻量级锁 (Lightweight Locking):当出现少量线程竞争时,通过 CAS 和自旋实现。
    • 重量级锁 (Heavyweight Locking):也就是我们熟悉的synchronized,需要操作系统互斥量,开销最大。
    • GC 标记 (Marking):在垃圾回收期间,Mark Word 可能会被用来标记对象状态。
  • 偏向线程 ID (Biased Thread ID):如果对象处于偏向锁状态,会记录偏向的线程 ID。

  • Epoch:偏向锁的时代计数器。

看,一个 8 字节(或 4 字节)的 Mark Word 竟然塞了这么多东西!这简直就是个微缩版的情报局!🕵️‍♀️

2.2 Klass Pointer (类型指针)

这部分是对象头的另一个重要组成部分,它占用 4 字节(32 位 JVM)或 8 字节(64 位 JVM)。
这个指针指向对象的类元数据 (Klass Metadata)。
通俗地讲,它告诉 JVM:“嘿!这个对象是MyClass类型!它的方法、字段定义都在MyClass的元数据里呢!”
通过这个指针,JVM 才能知道这个对象是什么类型,它有哪些方法,有多少个成员变量等等。

指针压缩 (Compressed Ordinary Pointers):在 64 位 JVM 中,为了节省内存空间,JVM 会默认开启指针压缩。
原本一个 64 位的指针需要 8 字节,压缩后只需要 4 字节!这样可以大大减少内存开销。但它不是没有代价的,每次访问都需要解压缩,会有一点点性能损耗。但总的来说,内存省下来了,GC 压力小了,大部分情况下是划算的!💰

2.3 数组长度 (Array Length)

如果对象是一个数组(比如new int[10]),那么在对象头里还会多出一个 4 字节的数组长度字段,用来记录数组的长度。
普通对象是没有这个字段的。

2.4 对象内存布局示意图(64位 JVM + 指针压缩)
--------------------------------------------------------------------------------------------------------------------|对象头(Object Header)|实例数据(Instance Data)|对齐填充(Padding)|--------------------------------------------------------------------------------------------------------------------|MarkWord(8字节)|KlassPointer(4字节)|各种成员变量|凑够8字节倍数||(哈希码、GC年龄、锁状态、偏向锁ID等)|(指向类元数据)|(从父类到子类)||--------------------------------------------------------------------------------------------------------------------

如果是个数组:

--------------------------------------------------------------------------------------------------------------------|对象头(Object Header)|数组数据(Array Data)|对齐填充(Padding)|--------------------------------------------------------------------------------------------------------------------|MarkWord(8字节)|KlassPointer(4字节)|数组长度(4字节)|数组元素|凑够8字节倍数|--------------------------------------------------------------------------------------------------------------------

为啥需要对齐填充?
因为 JVM 要求对象的大小必须是 8 字节的倍数!这样可以提高 CPU 访问内存的效率。如果对象实际大小不是 8 字节的倍数,就会自动补齐,这就是对齐填充(有时候也是内存浪费的一部分,但为了性能,值了!)。

3. 实战:自己动手看看对象头!

光说不练假把式!咱们可以用一个神器来窥探对象头:JOL (Java Object Layout)。

首先,你需要在你的 Maven 项目里添加依赖:

<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.17</version></dependency>

然后,写一段代码,打印出对象的布局:

importorg.openjdk.jol.info.ClassLayout;importorg.openjdk.jol.vm.VM;// 运行参数:-XX:+UseCompressedOops (默认开启,用于64位JVM指针压缩)// 如果想看未压缩的,可以 -XX:-UseCompressedOopspublicclassObjectLayoutDemo{staticclassMyObject{inti;// 4 字节booleanb;// 1 字节Strings;// 8 字节 (引用类型,指针压缩后是 4 字节)longl;// 8 字节}publicstaticvoidmain(String[]args){System.out.println(VM.current().details());// 打印 JVM 详情,包括是否启用指针压缩MyObjectobj=newMyObject();System.out.println("----------------------------------------------");System.out.println("MyObject 对象的内存布局:");System.out.println(ClassLayout.parseInstance(obj).toPrintable());System.out.println("----------------------------------------------");System.out.println("数组对象的内存布局:");int[]arr=newint[5];System.out.println(ClassLayout.parseInstance(arr).toPrintable());}}

运行这段代码,你会得到类似这样的输出(不同 JVM 版本和配置可能略有差异):

#VMversion:JDK17.0.8,OpenJDK64-Bit ServerVM,17.0.8+7#...(省略部分JVM信息)# Object alignment:8bytes.# Field sizes:# _byte:1# _char:2# _short:2# _int:4# _long:8# _float:4# _double:8# _boolean:1# _reference:4(Compressed Oops enabled)----------------------------------------------MyObject 对象的内存布局: org.example.ObjectLayoutDemo$MyObject object internals:OFFSETSIZETYPEDESCRIPTIONVALUE04(object header)01000000(00000001000000000000000000000000)(1)44(object header)00000000(00000000000000000000000000000000)(0)84(object header)3e000021(00111110000000000000000000100001)(553648670)124int MyObject.i0168long MyObject.l0244boolean MyObject.bfalse284String MyObject.snull320(loss due to the next object alignment)Instance size:32bytes Space losses:0bytes internal+0bytes external=0bytes total

来,老哥给你划重点!🤓

  • OFFSET 0-4-8:这就是对象头!Mark Word占了 8 字节,Klass Pointer占了 4 字节(因为Compressed Oops enabled)。
  • OFFSET 12:MyObject.i,int类型,占 4 字节。
  • OFFSET 16:MyObject.l,long类型,占 8 字节。
  • OFFSET 24:MyObject.b,boolean类型,占 1 字节。
  • OFFSET 28:MyObject.s,String类型,这是个引用,占 4 字节(因为指针压缩)。
  • Instance size: 32 bytes:总大小是 32 字节,是 8 的倍数,完美对齐!

数组的布局也很有意思:

----------------------------------------------数组对象的内存布局:[Iobject internals:OFFSETSIZETYPEDESCRIPTIONVALUE04(object header)01000000(00000001000000000000000000000000)(1)44(object header)00000000(00000000000000000000000000000000)(0)84(object header)3e000021(00111110000000000000000000100001)(553648670)124(array length)51620int[I.<elements>N/AInstance size:32bytes

看!OFFSET 12多了个(array length)字段,占了 4 字节!这就是我前面说的数组特有的部分!

是不是感觉整个世界都清晰了?🤩 你现在对new出来的对象,有了更深刻的理解!

4. 写在最后:知其然,更要知其所以然!

咱们今天聊的这些,从一个简单的new关键字,挖到了 JVM 内部的对象创建流程,再到每个对象自带的“身份证”——对象头。

这有什么用呢?

  • 当你调优 JVM 参数,比如 TLAB 大小、GC 策略时,你就能明白背后的原理。
  • 当你遇到内存泄漏或者 OOM 时,能够更准确地分析是不是大量小对象堆积或者大对象分配不当。
  • 当你深入理解synchronized锁升级过程时,你会发现 Mark Word 简直是主角!
  • 当你进行性能分析,想知道对象占用多少内存时,JOL 工具和对象头知识就是你的火眼金睛!

所以啊,兄弟们,别再把 Java 当成一个“黑盒”语言了!深入了解它,你才能真正驾驭它,成为那个能让 JVM 都“颤抖”的全栈高手!💪

好了,今天的探险之旅就到这里。记得实践一下 JOL,你会发现更多有趣的细节!咱们下期接着聊 JVM 那些不为人知的故事!👋😎

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 1:55:53

OpenCore Legacy Patcher 指南:5 步给老 Mac 装上新版 macOS

OpenCore Legacy Patcher 指南&#xff1a;5 步给老 Mac 装上新版 macOS 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 2013 款的 iMac 收不到系统更新&…

作者头像 李华
网站建设 2026/10/5 1:53:52

CarSim与Simulink联合仿真:Driver Model与5个Driver Sensors闭环控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华