对象创建的过程
https://mp.weixin.qq.com/s/y82t0a4dTBZwgY6MRnZDIw
创建对象的方式有4种:new 关键字、反射机制、Object 类的 clone 方法、反序列化。
针对 new 关键字的方式,来谈谈对象创建的过程,例如 Demo 类:
// 创建Demo类的实例对象 Demo demo = new Demo(); // 定义Demo类 public class Demo { private int i; private String str = "初始值"; // 构造代码块 { /* do something */ } // 静态代码块 static { /* do something */ } // 构造方法 public Demo() { } public int getI() { return i; } public void setI(int i) { this.i = i; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } }
创建对象的过程一共5个步骤,如下所示:
一、检查类是否已经被加载
当 JVM 执行到 字节码 new 指令时,首先会到 常量池 中定位到 Demo 类的符号引用,通过符号引用检查 Demo 类是否已被加载。
如果 Demo 类没有被被加载,就必须先执行加载过程。
二、为对象分配内存空间
Demo 类的加载检查通过后,JVM 将会从堆中为 new Demo() 创建的实例对象分配确定大小的内存。
分配内存的方式有两种:指针碰撞、空闲列表
-
指针碰撞
垃圾回收算法是标记-压缩算法,堆内存的空间很整齐的,没有碎片空间,使用中的内存放在一端,空闲的内存在另一端,在中间放置一个指针作为分界点。
分配内存时,只需要将指针往空闲内存的那一端挪动一段与对象大小的距离。
-
空闲列表
垃圾回收算法是标记-清除算法,堆内存的空间很凌乱的,存在碎片空间,使用的内存和空闲的内存相互交错的在一起。
这种情况下,JVM 需要维护一个空闲列表,记录哪些是空闲内存,分配内存的时候从空闲列表中找到一块足够大的内存空间存放对象,并更新空闲列表
三、将分配到的内存空间初始化零值(不包括对象头)
为对象的字段进行初始化默认值,保证对象即使没有赋初值也可以直接使用。
int 初始化为 0,boolean初始化为false,string 初始化为 null。
四、为对象进行必要的设置
设置对象头(Object Head),包括这个对象所属的类,类的元数据信息,对象的哈希码,对象的 GC 分代年龄等信息。
五、执行构造方法
按照顺序执行:
-
执行 Class 文件中的 <init>() 方法
-
初始化对象的字段:例如 str 初始化为 "初始值"
-
静态代码块
-
构造代码块
-
构造方法
对象在堆内存中的存储布局划分为三个部分:对象头、实例数据、对齐填充。
对象头包括两部分信息:对象自身的运行时数据、类型指针。
一、对象头
对象头包括两部分信息:对象自身的运行时数据、类型指针。
-
对象自身的运行时数据(Mark Word)
这些数据包括:哈希码、GC 分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等。
这部分数据的长度在32位和64位的 JVM 中分别为:32 bit、64 bit。
-
类型指针
就是对象指向它的类型元数据的指针,可以理解为 demo 指向 Demo.class。
二、实例数据
实例数据就是对象中的所有字段内容,包括从父类继承的所有字段内容。
例如 demo 中的 i 和 str 。
三、对齐填充
对齐填充并不是必然存在的,也没有特别的含义,它仅仅是起到占位符的作用。
因为 JVM 存储对象的内存大小必须是8字节的整数倍。
那么,当对象的大小不是8字节的整数倍,意味着对象申请到的内存大小,必然大于对象的大小,剩余的内存空间就是对齐填充。
对象的访问定位
Demo demo = new Demo();
这一行代码,demo 只是对象的引用,并不是真正创建的对象,demo 在内存中存储的是 new Demo() 实例对象的地址。
demo 存储在栈,new Demo() 所创建的实例对象存储在堆。
demo 访问 new Demo() 所创建的实例对象有两种方式:句柄、直接指针。
一、句柄
句柄池存在堆,那么 demo 存储的就是 new Demo() 的句柄地址,句柄中包含了对象的实例数据和类型数据的实际地址。
以 Demo 类为例,如下图所示:
二、直接指针
直接指针 就是 demo 存储的就是 new Demo() 的实际地址。
以 Demo 类为例,如下图所示:
句柄 相比于 直接访问 需要多一次间接访问的开销,不过句柄访问最大的好处就是灵活,因为引用存储的是句柄地址,句柄地址是不会发生改变的。
这相当于多了一层抽象,句柄就是这一层抽象。
这意味着,即使对象的实际地址发生改变,只需要修改句柄中的实例数据指针的地址,不需要修改引用的值,即 demo 存储的值。
垃圾回收在使用 标记-压缩算法 的情况下,对象的实际地址发生改变是十分普遍的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)