咱们先编个小故事:你是 Android 小白 "小安",想做个「用户信息 App」------ 需要一个能存 "用户名" 和 "年龄" 的 "专属小屋"(对应代码里的User
对象)。你写了句User user = new User("小安", 25);
,按下运行键后,手机里的 "大管家"(ART 虚拟机,Android 现在不用 JVM 了,但核心逻辑相通)就开始帮你 "盖房子"。
下面咱们跟着这个 "盖房子" 的故事,一步步拆透 new 对象的原理,最后再用代码和时序图固化认知。
一、先准备 "盖房子的图纸":User 类是什么?
在 new 对象前,你得先告诉 "大管家":"我要的房子长这样!"------ 这就是User
类的作用,它是对象的 "设计图纸" 。
先看你写的 "图纸代码":
java
// 这是"用户小屋"的设计图纸:User类
class User {
// 图纸里规定的"房间":用户名、年龄(成员变量)
String name;
int age;
// 图纸里规定的"装修方案":构造方法(必须和类名一样,没有返回值)
public User(String name, int age) {
this.name = name; // 给"用户名房间"贴标签
this.age = age; // 给"年龄房间"填数字
}
}
// 你(调用者)发起"盖房子"请求
public class Main {
public static void main(String[] args) {
// 关键一步:喊"大管家"按图纸盖房子!
User user = new User("小安", 25);
}
}
你以为new User(...)
是直接 "动工盖房"?错!"大管家" ART 很严谨,动工前要做 5 件事,咱们继续看故事。
二、"盖房子" 的 5 步魔法:new 对象的完整流程
咱们把 ART 大管家的工作拆解成 5 个步骤,每个步骤都对应 "盖房子" 的真实场景,再贴代码解释:
步骤 1:查图纸 ------ART 检查 "User 类是否已加载"
你喊 "盖 User 小屋",大管家第一反应是:"我手里有 User 的图纸吗?"------ 这对应类加载检查 :ART 会先去 "类缓存区" 查User.class
是否已被加载成Class
对象(图纸备案)。
-
如果没备案(类未加载):大管家会立刻执行「类加载流程」(3 小步),相当于 "找图纸→审图纸→备案图纸":
-
加载 :去 APK 的 dex 文件里找
User.class
(找图纸原件),把二进制数据读进内存,生成一个Class
对象(图纸复印件备案)。 -
链接:审图纸 + 做准备:
- 验证:检查图纸有没有错(比如语法错误、是否符合 Android 规范),防止 "豆腐渣工程"。
- 准备:给静态变量分配内存并设默认值(比如
static int count = 10
会先设为 0,等初始化时再改成 10)。 - 解析:把图纸里的 "符号引用"(比如
String
)换成内存里的 "直接引用"(找到String
类的实际地址)。
-
初始化 :执行静态代码块(比如
static { count = 10; }
),给静态变量赋真实值 ------ 这步完成,图纸才算正式备案。
-
-
如果已备案(类已加载):直接跳过类加载,进入下一步。
步骤 2:找空地 ------ART 在堆内存分配空间
图纸备案好了,大管家要带你来 "土地储备库"------堆内存(Android 里所有对象都住在堆里),给你划一块地盖房子。
怎么划地?分两种情况,像极了现实中找地:
分配方式 | 场景比喻 | 原理说明 |
---|---|---|
指针碰撞 | 小区里的地是 "整齐排空" 的 | 堆内存里的空闲空间是连续的,大管家把 "空闲指针" 往前挪一块(挪的大小 = User 对象需要的内存),挪出的空间就是你的地。 |
空闲列表 | 小区里的地是 "东一块西一块" | 堆内存碎片化,大管家查 "空闲土地记录表",找一块大小刚好的地划给你。 |
问题来了:如果有 10 个线程同时喊 "盖房子",抢同一块地怎么办?(并发安全问题)大管家有两个解决办法:
- CAS + 失败重试:大管家盯着抢地过程,谁先抢到算谁的,没抢到的重试(CAS 是 "Compare And Swap",比较并交换,保证原子性)。
- TLAB(本地线程分配缓冲) :大管家提前给每个线程分一块 "专属小空地",线程盖房子先在自己的小空地里划,用完了再向大管家要 ------ 避免抢地,效率更高(Android 默认用这个)。
步骤 3:毛坯房 ------ART 执行 "零值初始化"
地划好了,大管家不会让你直接装修,而是先把地弄成 "毛坯房":给所有成员变量赋默认零值。
对应代码里的User
类:
name
(String 类型)默认设为null
(空房间);age
(int 类型)默认设为0
(空数字)。
为什么要做这步?防止 "房间里有垃圾"!比如如果不设零值,age
可能是内存里残留的随机数(比如 12345),导致 App 显示错误。
步骤 4:挂门牌 ------ART 设置 "对象头"
毛坯房弄好,大管家要在门口挂个 "门牌"------对象头(Object Header) ,这是每个对象的 "身份证",里面存 3 类关键信息:
信息类别 | 门牌内容举例 | 作用 |
---|---|---|
类元信息 | "户型:User" | 告诉 ART:这个对象是哪个类的实例(能找到 User 类的Class 对象)。 |
对象标识信息 | "门牌号:0x123456"(哈希码) | 唯一标识这个对象,GC(垃圾回收)时用来认对象。 |
锁状态信息 | "未上锁" | 处理多线程同步(比如synchronized 锁就靠这个)。 |
比如你的 User 对象,门牌上会写:"户型 User,门牌号 0x123456,未上锁"------ART 一看门牌就知道这房子的所有基础信息。
步骤 5:精装修 ------ART 调用构造方法()
最后一步!大管家按照你写的 "装修方案"------构造方法(User 的构造函数) ,给毛坯房做 "精装修"。
对应代码:
java
// 构造方法执行:给成员变量赋真实值(装修)
public User(String name, int age) {
this.name = name; // 把"小安"贴到name房间(从null→"小安")
this.age = age; // 把25填到age房间(从0→25)
}
这里要注意:构造方法不是 "创建对象",而是 "初始化对象" !对象在步骤 2 分配内存时就已经 "诞生" 了,构造方法只是给它填内容。
等构造方法执行完,"用户信息小屋" 就彻底建好啦!大管家把 "房子的地址"(对象引用,比如0x123456
)交给你(赋值给user
变量),以后你通过user.name
、user.age
,就能根据地址找到房子里的内容。
三、时序图:一眼看清整个调用过程
为了更直观,咱们用时序图把 "调用者→ART→堆内存" 的交互过程画出来(用 Mermaid 语法,你可以复制到支持 Mermaid 的工具里查看):

堆内存User.class堆内存(土地库)User.class(图纸)ARTART(大管家)调用者(Main线程)堆内存User.class堆内存(土地库)User.class(图纸)ARTART(大管家)调用者(Main线程)alt[未加载]类加载子系统内存分配模块对象初始化模块发起new User("小安",25)请求检查User类是否已加载?加载User.class(读图纸)链接(验证→准备→解析)初始化(执行静态代码)User类加载完成(备案图纸)内存分配模块:用TLAB/指针碰撞分配空间在堆中划分User对象所需空间内存分配模块:给成员变量设零值(name=null, age=0)对象初始化模块:设置对象头(类元/哈希码/锁状态)调用User构造方法()执行构造方法:name="小安", age=25返回对象引用(如0x123456)给user变量
四、必记的 3 个关键结论
- new 对象不是一步到位:是 "查图纸→找地→毛坯→挂门牌→装修"5 步,构造方法是最后一步。
- 对象住堆,引用住栈 :"房子"(对象)在堆内存,"房子地址"(
user
引用)在栈内存(调用者线程的栈)。 - 类只加载一次 :同一个类(比如 User)在 App 运行期间,ART 只会加载一次,后续 new 对象直接复用已加载的
Class
对象。
通过这个 "盖房子" 的故事,你应该能看懂 new 对象的底层逻辑了吧?下次再写new
的时候,就想想背后有个 ART 大管家在帮你忙前忙后~