news 2026/4/21 2:17:06

基础篇五 你以为 new 一下就完事了?Java 对象诞生背后的五道关卡

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基础篇五 你以为 new 一下就完事了?Java 对象诞生背后的五道关卡

文章目录

    • 一、全流程概览
    • 二、第一关:类加载检查——JVM 认不认识你?
    • 三、第二关:分配内存——给对象找个"房子"
      • 并发安全:两个人抢同一间房怎么办?
    • 四、第三关:零值初始化——毛坯房刷白墙
    • 五、第四关:设置对象头——装门牌号和监控
      • 对象头包含什么?
    • 六、第五关:执行构造方法——精装修
      • 完整执行顺序
    • 七、回到全貌:一张图串起来
    • 八、面试速答模板

个人网站

写代码时,new Person()一敲,对象就出来了——就像你在餐厅点个菜,菜就端上来了。但你有没有想过:厨房里到底经历了什么?食材采购、清洗切配、大火爆炒、摆盘装饰……你看到的只是最后一秒。

Java 对象的创建也是一样。你以为只是一句new,JVM 却在背后默默跑了五道流程,哪一道出了问题,你的对象都活不了。今天我们就去"后厨"看看,一个 Java 对象到底是怎么被造出来的。

一、全流程概览

一个对象从new到可用,要经过以下五步:

new Person() │ ├─ ① 类加载检查 → 你这个类 JVM 认识吗? ├─ ② 分配内存 → 给对象找个"房子" ├─ ③ 零值初始化 → 毛坯房刷白墙(字段设默认值) ├─ ④ 设置对象头 → 装门牌号和监控(HashCode、GC 分代年龄等) └─ ⑤ 执行构造方法 → 精装修(你写的赋值逻辑)

下面逐步拆解,保证你看完就懂。

二、第一关:类加载检查——JVM 认不认识你?

当 JVM 遇到new Person(),第一件事不是分配内存,而是问:Person 这个类加载了吗?

就像你去酒店入住,前台先查你有没有预约记录。没有?对不起,先去办手续。

Personp=newPerson();

JVM 会检查Person这个符号引用是否已在方法区中:

  • 已加载→ 直接进入下一步
  • 未加载→ 触发类加载过程(加载 → 验证 → 准备 → 解析 → 初始化),加载完再继续

这就是为什么有时候new一个对象会触发一堆 static 代码块执行——类加载的初始化阶段会执行<clinit>()方法,里面包含了所有 static 变量赋值和 static 代码块。

classPerson{static{System.out.println("类加载了!");// new 之前会先执行}}newPerson();// 控制台输出:类加载了!

三、第二关:分配内存——给对象找个"房子"

类加载通过后,JVM 要给对象分配一块内存。对象需要多大?JVM 根据类信息一算就知道——就是所有实例变量(不包括静态变量)占的空间。

分配方式取决于堆内存是否规整,而是否规整取决于垃圾收集器是否带压缩整理:

分配方式适用场景原理
指针碰撞(Bump the Pointer)堆内存规整(如 Serial、ParNew、CMS 带压缩)移动指针即可,高效
空闲列表(Free List)堆内存不规整(如 CMS 不压缩模式)维护一个"哪些空间空闲"的列表,分配时查找
指针碰撞: ┌────┬────┬────┬────┬──────────────────────┐ │ 对象│ 对象│ 对象│ 对象│ 空闲空间 │ └────┴────┴────┴────┴──────────────────────┘ ↑ 指针 分配后指针右移即可 空闲列表: ┌────┬ ┬────┬ ┬────┬──────────────┐ │ 对象│空闲│ 对象│ 空闲 │ 对象│ 空闲 │ └────┴ ┴────┴ ┴────┴──────────────┘ 需要查表找到合适大小的空闲块

并发安全:两个人抢同一间房怎么办?

对象分配是高频操作,线程 A 和线程 B 可能同时抢同一块内存。JVM 用两种方案解决:

方案一:CAS + 失败重试

对分配动作做原子操作,抢到了就分配,抢不到就重试。

方案二:TLAB(Thread Local Allocation Buffer)

每个线程在 Eden 区预先分一小块私有空间,先在自己的地盘上分配,用完了再去公共区域用 CAS 抢。大部分情况下都在 TLAB 里分配,几乎无竞争。

// 开启 TLAB(默认开启)-XX:+UseTLAB

面试加分点:TLAB 虽好,但空间有限。对象太大或 TLAB 用完,还是要走 CAS 在 Eden 区分配。

四、第三关:零值初始化——毛坯房刷白墙

内存分到后,JVM 会把这块空间全部初始化为零值(不包括对象头):

类型零值
int0
long0L
float0.0f
double0.0d
booleanfalse
char‘\u0000’
引用类型null
classPerson{intage;Stringname;booleanalive;}Personp=newPerson();// 此时:age = 0, name = null, alive = false// 但你一行赋值代码都还没执行!

这一步保证了 Java 代码即使不赋初值也不会拿到随机垃圾值——C/C++ 程序员流下了羡慕的泪水。

注意:这里的零值初始化和构造方法中的赋值是两回事。如果构造方法里写了age = 18,那先被初始化为 0,再被构造方法改为 18。

五、第四关:设置对象头——装门牌号和监控

这一步是很多人忽略的,但对 JVM 至关重要。JVM 会在对象头中设置:

对象头包含什么?

┌─────────────────────────────────────────────┐ │ 对象头 │ ├──────────────┬──────────────┬───────────────┤ │ Mark Word │ 类型指针 │ 数组长度 │ │ (8 字节) │ (4/8 字节) │ (仅数组对象) │ └──────────────┴──────────────┴───────────────┘

Mark Word(标记字段)——存的是重量级信息:

  • 对象的 HashCode(第一次调用时才计算并存入)
  • GC 分代年龄(经过几次 GC 还活着)
  • 锁状态标志(无锁、偏向锁、轻量级锁、重量级锁)
  • 偏向线程 ID

类型指针——指向类元数据,JVM 通过它知道这个对象是Person还是Dog

数组长度——只有数组对象才有,普通对象不需要。

面试常考点:synchronized 的锁升级过程,就记录在 Mark Word 中。从无锁 → 偏向锁 → 轻量级锁 → 重量级锁,Mark Word 的存储内容会随之变化。

六、第五关:执行构造方法——精装修

前面四步都是 JVM 自动完成的,到这一步终于轮到你的代码登场了。

JVM 会执行<init>()方法,也就是你写的构造方法:

classPerson{intage=18;// 实例变量赋值 → 编译后放进 <init>()Stringname="张三";// 实例变量赋值 → 编译后放进 <init>()Person(){// 构造方法age=25;// 会覆盖上面的 18System.out.println("对象创建完毕!");}}newPerson();// 执行顺序:// 1. 零值初始化(age=0, name=null)// 2. 实例变量赋值(age=18, name="张三")// 3. 构造方法体(age=25, 打印"对象创建完毕!")

完整执行顺序

如果你有父类,顺序更完整:

1. 父类静态变量赋值 + 父类 static 代码块(类加载时,只执行一次) 2. 子类静态变量赋值 + 子类 static 代码块(类加载时,只执行一次) 3. 父类实例变量赋值 4. 父类构造方法 5. 子类实例变量赋值 6. 子类构造方法
classAnimal{static{System.out.println("1: Animal 静态代码块");}{System.out.println("3: Animal 实例代码块");}Animal(){System.out.println("4: Animal 构造方法");}}classDogextendsAnimal{static{System.out.println("2: Dog 静态代码块");}{System.out.println("5: Dog 实例代码块");}Dog(){System.out.println("6: Dog 构造方法");}}newDog();// 输出:1 → 2 → 3 → 4 → 5 → 6

为什么静态代码块只执行一次?因为类加载只发生一次。而实例代码块和构造方法是每次 new 都执行的。

七、回到全貌:一张图串起来

new Dog() │ ├─ ① 类加载检查 │ Animal 已加载?→ 是 │ Dog 已加载? → 否 → 触发类加载 │ ├─ 加载 Animal.class → 执行 Animal <clinit>(输出 1) │ └─ 加载 Dog.class → 执行 Dog <clinit>(输出 2) │ ├─ ② 分配内存(堆上分配 Dog 实例所需空间) │ ├─ ③ 零值初始化(所有字段设为默认值) │ ├─ ④ 设置对象头(Mark Word + 类型指针) │ └─ ⑤ 执行 <init>() ├─ 调用父类 <init>() │ ├─ Animal 实例代码块(输出 3) │ └─ Animal 构造方法(输出 4) ├─ Dog 实例代码块(输出 5) └─ Dog 构造方法(输出 6)

八、面试速答模板

Q:Java 对象的创建过程?

A:五步——① 类加载检查:确保类已被加载,未加载则先触发类加载;② 分配内存:根据类信息计算大小,在堆上分配,方式有指针碰撞和空闲列表;③ 零值初始化:将内存空间初始化为零值,保证字段有默认值;④ 设置对象头:存入 HashCode、GC 分代年龄、锁状态、类型指针等;⑤ 执行构造方法:按先父类后子类的顺序执行实例变量赋值和构造方法体。

Q:对象分配内存时的并发问题怎么解决?

A:两种方案——① CAS + 失败重试,保证分配动作的原子性;② TLAB(线程本地分配缓存),每个线程在 Eden 区预分一小块私有空间,优先在私有空间分配,用完再用 CAS 在公共区域分配。TLAB 默认开启,能消除大部分竞争。

Q:对象头里存了什么?

A:主要存两块信息——Mark Word 存储 HashCode、GC 分代年龄、锁状态标志、偏向线程 ID 等;类型指针指向类元数据,JVM 据此确定对象属于哪个类。数组对象还会额外存储数组长度。
相关文章

原文阅读


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

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

AI 术语通俗词典:平均绝对误差(MAE)

平均绝对误差是统计学、数据分析、机器学习和人工智能中非常常见的一个术语。它用来描述模型预测结果与真实结果之间的平均偏差大小。换句话说&#xff0c;平均绝对误差是在回答&#xff1a;模型平均而言大约会偏离真实值多少。如果说残差回答的是“某一个样本到底偏了多少”&a…

作者头像 李华
网站建设 2026/4/21 2:08:56

工业巡检机器人联网方案:IR615 如何打造双链路稳定通信与远程运维

一、行业背景与痛点在石油化工、电力、冶金等工业场景中&#xff0c;巡检机器人已成为替代人工完成高危、重复性巡检任务的核心设备&#xff0c;承担着腐蚀老化破损识别、表计识别、跑冒滴漏检测、阀位 / 阀门开度识别等关键工作。这类场景普遍存在以下通信与运维难题&#xff…

作者头像 李华
网站建设 2026/4/21 1:58:16

GPT Pro悄悄升级速度暴增4倍,网友热议GPT-5.5何时到来?

本报讯 近日&#xff0c;大批ChatGPT Pro用户在社交媒体上发现自家AI助手发生了"神突变"——模型响应速度大幅提升&#xff0c;代码生成和视觉理解能力明显增强&#xff0c;而OpenAI方面却未进行任何官方公告。这场"静默革命"正在悄然改变大模型的竞争格局…

作者头像 李华