Java对象创建与内存分配
理解对象创建过程有助于理解JVM内存管理和性能优化。
对象创建流程
Java
┌─────────────┐
│ new指令 │
└─────────────┘
↓
┌─────────────┐
│ 类加载检查 │ ← 是否已加载
└─────────────┘
↓
┌─────────────┐
│ 分配内存 │ ← 堆中分配
└─────────────┘
↓
┌─────────────┐
│ 初始化零值 │ ← 默认值
└─────────────┘
↓
┌─────────────┐
│ 设置对象头 │ ← 元数据
└─────────────┘
↓
┌─────────────┐
│ 执行init方法 │ ← 构造器初始化
└─────────────┘
↓
┌─────────────┐
│ 返回引用 │
└─────────────┘
详解各步骤
1. 类加载检查
Java
Object obj = new Object();
// JVM检查Object类是否已加载、链接、初始化
// 若未加载,则执行类加载过程
2. 分配内存
两种分配方式:
| 方式 | 原理 | 适用场景 |
|---|---|---|
| 指针碰撞 | 移动指针分配 | 堆内存规整(Serial、ParNew) |
| 空闲列表 | 从列表中选择空闲块 | 堆内存不规整(CMS) |
并发安全问题:
| 方案 | 描述 |
|---|---|
| CAS + 失败重试 | 保证更新原子性 |
| TLAB | Thread Local Allocation Buffer,每个线程独立分配区 |
Java
// JVM参数
-XX:+UseTLAB // 启用TLAB
3. 初始化零值
Java
public class User {
private int age; // 初始化为0
private boolean active; // 初始化为false
private String name; // 初始化为null
}
对象字段在构造器执行前已初始化为默认值
4. 设置对象头
对象头包含:
- Mark Word:锁状态、GC年龄、哈希码
- 类型指针:指向类元数据
5. 执行init方法
Java
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name; // 初始化
this.age = age;
}
}
// 构造器即为init方法,完成对象初始化
对象内存布局
Java
┌─────────────────────────────────┐
│ 对象结构 │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐│
│ │ 对象头 ││
│ │ ┌─────────┬─────────┐ ││
│ │ │Mark Word│类型指针 │ ││
│ │ │ 8字节 │ 4/8字节│ ││
│ │ └─────────┴─────────┘ ││
│ └─────────────────────────────┘│
│ ┌─────────────────────────────┐│
│ │ 实例数据 ││
│ │ 对象字段(不定长度) ││
│ └─────────────────────────────┘│
│ ┌─────────────────────────────┐│
│ │ 对齐填充 ││
│ │ 保证8字节对齐 ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘
Mark Word结构
64位JVM Mark Word:
Java
┌────────────────────────────────────┐
│ Mark Word │
├────────────────────────────────────┤
│ 无锁: │ hashcode │ age │ 01 │
│ 偏向锁: │ threadID │ age │ 01 │
│ 轻量锁: │ lock record ptr │ 00 │
│ 重量锁: │ monitor ptr │ 10 │
│ GC标记: │ 空 │ 11 │
└────────────────────────────────────┘
对象访问方式
| 方式 | 描述 | 特点 |
|---|---|---|
| 直接指针 | 引用直接指向对象 | 访问快,对象移动需更新引用 |
| 间接访问 | 引用指向句柄,句柄指向对象 | 引用稳定,对象移动只更新句柄 |
Java
// 直接指针(HotSpot使用)
reference → object
// 间接访问
reference → handle → object
→ class metadata
内存分配策略
对象优先在Eden分配
Java
Object obj = new Object();
// 大多数对象在Eden区分配
// Eden满时触发Minor GC
大对象直接进老年代
text
// 大对象参数
-XX:PretenureSizeThreshold=1MB // 超过1MB直接进老年代
长期存活进老年代
text
// 年龄阈值
-XX:MaxTenuringThreshold=15 // GC年龄超过15进老年代
// Survivor区年龄计数
// 每次Minor GC年龄+1
动态年龄判定
text
// Survivor区对象总大小超过一半
// 年龄最大的对象进老年代
实际示例
text
public class ObjectCreationDemo {
public static void main(String[] args) {
// 创建对象
User user = new User("张三", 25);
/*
* 创建过程:
* 1. 检查User类是否加载
* 2. 堆中分配内存(假设TLAB)
* 3. 初始化零值:name=null, age=0
* 4. 设置对象头
* 5. 执行构造器:name="张三", age=25
* 6. 返回引用给user变量
*/
}
}
注意事项
对象分配默认在TLAB中,减少并发冲突
大对象避免在年轻代复制,直接进老年代
对象头大小影响内存占用,可用-XX:+UseCompressedOops压缩指针
理解对象创建有助于排查内存问题
要点总结
- 对象创建:类加载检查 → 分配内存 → 初始化零值 → 设置对象头 → 执行init
- 内存分配方式:指针碰撞(规整堆)或空闲列表(不规整堆)
- 对象结构:对象头 + 实例数据 + 对齐填充
- 分配策略:优先Eden,大对象直接老年代,长期存活进老年代
- HotSpot使用直接指针访问对象
📝 发现内容有误?点击此处直接编辑