Java基础语言进阶学习——2,对象创建机制与内存布局
对象创建机制与内存布局
学习目标:深入理解对象创建机制与内存布局
文章目录
一、对象创建机制——"对象的诞生之旅"
第1步:类加载检查 - "确认图纸"
第2步:分配内存 - "圈地皮"
第3步:内存空间初始化(零值初始化)- "毛坯房交付"
第4步:设置对象头 - "挂上门牌和档案"
二、对象内存布局 - "对象的身体结构"
1. 对象头
2. 实例数据 - "对象的血肉"
3. 对齐填充 - "为了整齐的补位"
三,实战验证:用JOL工具查看对象内存布局
1. 导入依赖:
2. 查看对象布局:
3.JOL 工具的作用
4.对象内存布局分析
1. Object对象的内存布局
2. Person对象的内存布局
5.运行结果的核心信息
四,总结
1.对象创建机制:
2.对象内存布局:
3.为什么重要:
一、对象创建机制——"对象的诞生之旅"
当我们写下 Person p = new Person("张三", 25); 这行简单的代码时,JVM在幕后为我们上演了一场精彩的"创世"大戏。
详细步骤分解:
第1步:类加载检查 - "确认图纸"
-  
动作:JVM遇到
new关键字,首先去方法区的运行时常量池中查找Person类的符号引用。 -  
检查:这个
Person类是否已经被加载、验证、准备、解析和初始化? -  
如果未加载:立即触发类加载过程。这就像盖房子前,要先确认设计图纸是合法且完整的。
 -  
示例:如果是第一次创建
Person对象,JVM会加载Person.class文件,验证其安全性,为静态变量(如static int count)分配内存并赋零值。 
第2步:分配内存 - "圈地皮"
-  
目标:在堆内存中划分一块大小确定的内存空间。对象需要多大,在类加载完成后就已经确定了。
 -  
分配方式(由垃圾收集器GC决定):
a) 指针碰撞
 
假设堆内存布局:[已用][已用][BORDER][空闲][空闲][空闲]
分配一个Person对象(假设24字节)
分配后:[已用][已用][新Person对象][BORDER][空闲][空闲
-  
适用:堆内存规整(如Serial, ParNew等GC)
 -  
原理:用一个指针作为分界点,分配内存就是简单的移动指针
 
b) 空闲列表
假设堆内存布局:[已用][空闲][已用][空闲][空闲][已用]
虚拟机维护一个列表:空闲块1(地址1, 大小40B), 空闲块2(地址3, 大小80B)...
从中找一块足够大的空间分配给新对象
-  
适用:堆内存不规整(如CMS GC)
 -  
原理:维护一个"空闲地址登记表",从中找到足够大的空间
 
第3步:内存空间初始化(零值初始化)- "毛坯房交付"
-  
动作:将分配到的内存空间(除了对象头)都设置为零值
 -  
目的:保证对象的实例字段不赋初值就能使用,访问到的是数据类型的默认值
 
// 此时内存中的对象状态:
class Person {private String name;  // = null (零值)private int age;      // = 0 (零值)private boolean isStudent; // = false (零值)// 所有字段都是默认值,就像毛坯房
} 
第4步:设置对象头 - "挂上门牌和档案"
对象头就像是对象的"身份证",包含两类信息:
-  
Mark Word:存储对象自身的运行时数据
 -  
类型指针:指向方法区中的类元数据,表明"这个对象是Person类的实例"
 
第5步:执行构造方法 <init> - "精装修"
 
-  
动作:执行构造方法中的代码,按照程序员的意愿初始化对象
 
public class Person {private String name;private int age;public Person(String name, int age) {this.name = name;   // 把null替换成实际的"张三"this.age = age;     // 把0替换成实际的25}
} 
到此,一个真正可用的对象诞生了!
二、对象内存布局 - "对象的身体结构"
对象在堆内存中的存储布局就像人的身体结构,分为三部分:
1. 对象头
这是对象的"大脑和身份证",包含:
a) Mark Word - "状态身份证"
在64位JVM中,Mark Word是64位(8字节),结构灵活多变:
状态1:普通未锁定状态
[ 对象哈希码(31位) | 对象分代年龄(4位) | 0 | 01 ]状态2:偏向锁状态
[ 偏向线程ID(54位) | 时间戳(2位) | 1 | 01 ]状态3:轻量级锁状态
[ 指向栈中锁记录的指针(62位) | 00 ]状态4:重量级锁状态
[ 指向互斥量(重量级锁)的指针(62位) | 10 ]状态5:GC标记状态
[ 空,不需要记录信息 | 11 ]
b) 类型指针 - "户口本"
-  
指向方法区中的
Person类元数据 -  
让JVM知道"这个对象是Person类型"
 -  
在64位JVM中,开启指针压缩后通常是4字节
 
c) 数组长度(仅数组对象有)
-  
如果是
Person[]数组,这里会存储数组长度 
2. 实例数据 - "对象的血肉"
这是对象真正存储业务数据的地方,包含所有定义的字段
public class Person {// 实例数据区域开始private int id;           // 4字节private String name;      // 引用类型,开启指针压缩后4字节private int age;          // 4字节  private boolean married;  // 1字节private double salary;    // 8字节// 实例数据区域结束
} 
字段排列顺序规则:
-  
父类字段在子类之前
 -  
默认策略:按数据类型的长度从大到小排列
 -  
相同长度字段,引用类型优先
 
实际内存中可能的排列顺序:
[对象头(12字节)]
[double salary] 8字节 - 最长
[int id] 4字节
[int age] 4字节
[String name引用] 4字节 - 引用类型
[boolean married] 1字节 - 最短
[对齐填充] 7字节(为了8字节对齐)
3. 对齐填充 - "为了整齐的补位"
-  
目的:HotSpot要求对象大小必须是8字节的整数倍
 -  
原因:CPU读取内存时,按块读取(通常是8字节),对齐后能提高访问效率
 
计算上面Person对象的大小:
对象头: 12字节 (Mark Word 8字节 + 类型指针4字节)
实例数据: 8 + 4 + 4 + 4 + 1 = 21字节
总计: 12 + 21 = 33字节
需要对齐填充: 需要补7字节到40字节(8的倍数)
最终对象大小: 40字节
三,实战验证:用JOL工具查看对象内存布局
1. 导入依赖:
import org.openjdk.jol.info.ClassLayout; 
导入 JOL 工具的ClassLayout类,该类是 OpenJDK 提供的用于解析对象内存布局的核心类,可通过parseInstance()方法解析对象,toPrintable()方法输出内存布局详情。
2. 查看对象布局:
import org.openjdk.jol.info.ClassLayout;public class ObjectLayoutDemo {public static void main(String[] args) {Object obj = new Object();System.out.println(ClassLayout.parseInstance(obj).toPrintable());Person person = new Person(1, "张三", 25);System.out.println(ClassLayout.parseInstance(person).toPrintable());}
}class Person {private int id;private String name; private int age;private boolean married;public Person(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}
} 
详细分析:
-  
主类
ObjectLayoutDemo主方法main中执行了两个核心操作:- 创建
Object类实例obj,并打印其内存布局; - 创建
Person类实例person,并打印其内存布局。 
 - 创建
 -  
自定义类
Person包含 4 个私有字段和 1 个构造方法:- 字段:
id(int 类型)、name(String 引用类型)、age(int 类型)、married(boolean 类型); - 构造方法:初始化
id、name、age,married未显式初始化,默认值为false。 
 - 字段:
 
3.JOL 工具的作用
JOL(Java Object Layout)是用于分析 Java 对象在 JVM 中内存结构的工具,能直观展示对象的内存组成,包括:
- 对象头(Object Header):存储对象的元数据(如哈希码、锁状态、类指针等);
 - 实例数据(Instance Data):对象的字段数据(基本类型值或引用地址);
 - 对齐填充(Padding):为满足 JVM 对对象大小的对齐要求(通常是 8 字节的整数倍)而补充的空白字节。
 
4.对象内存布局分析
JVM 中对象的内存布局受JVM 位数(如 64 位)、指针压缩开关(默认开启)等因素影响。以下基于64 位 JVM + 默认指针压缩(UseCompressedClassPointers开启)分析。
1. Object对象的内存布局
 
Object类没有自定义字段,其内存仅包含对象头和可能的对齐填充。
-  
对象头(12 字节):
Mark Word(8 字节):存储对象的哈希码、GC 分代年龄、锁状态标记等信息(64 位 JVM 固定 8 字节);Class Pointer(4 字节):指向对象的类元数据(Object.class),因开启指针压缩,从 8 字节压缩为 4 字节。
 -  
对齐填充(4 字节):对象总大小需为 8 字节的整数倍(JVM 对齐要求)。对象头 12 字节不是 8 的倍数,需补充 4 字节填充,最终总大小为16 字节。
 
2. Person对象的内存布局
 
Person有 4 个字段,内存布局包括对象头、实例数据和对齐填充。
-  
对象头(12 字节):与
Object对象相同(Mark Word8 字节 +Class Pointer4 字节)。 -  
实例数据(13 字节):存储 4 个字段的数据(按字段定义顺序或 JVM 优化重排,此处按逻辑顺序分析):
id(int):4 字节(占偏移量 12-16);name(String 引用):4 字节(存储 String 对象的地址,占偏移量 16-20);age(int):4 字节(占偏移量 20-24);married(boolean):1 字节(占偏移量 24-25)。
 -  
对齐填充(7 字节):总大小 = 对象头(12) + 实例数据(13) = 25 字节,需对齐到 8 的整数倍(下一个倍数为 32),因此补充 7 字节填充,最终总大小为32 字节。
 
5.运行结果的核心信息
执行代码后,输出会包含类似以下的关键信息(不同 JVM 版本可能略有差异):
-  
Object对象:显示总大小 16 字节,包含 8 字节mark word、4 字节class pointer、4 字节padding。 -  
Person对象:显示总大小 32 字节,包含 12 字节对象头、13 字节实例数据(4+4+4+1)、7 字节填充,字段偏移量与上述分析一致。 
四,总结
1.对象创建机制:
-  
类加载 → 分配内存 → 零值初始化 → 设置对象头 → 执行构造方法
 -  
分配内存方式:指针碰撞(规整堆)、空闲列表(不规整堆)
 
2.对象内存布局:
-  
对象头:Mark Word(运行时数据)+ 类型指针(类信息)
 -  
实例数据:程序定义的字段,按特定策略排列
 -  
对齐填充:保证8字节对齐
 
3.为什么重要:
-  
性能优化:理解内存布局有助于写出缓存友好的代码
 -  
并发编程:synchronized锁信息存储在Mark Word中
 -  
故障诊断:内存溢出时知道对象在内存中的样子
 -  
面试必考:这是考察JVM理解深度的经典问题
 
记住这个生动的比喻:
对象创建就像造房子:确认图纸(类加载) → 圈地皮(分配内存) → 毛坯房(零值初始化) → 挂门牌(对象头) → 精装修(构造方法)
内存布局就像房子的结构:门牌档案(对象头) → 房间内容(实例数据) → 为了整齐的装饰(对齐填充
