jvm 创建对象的过程

本文详细解析了Java堆内存的分配机制,包括指针碰撞和空闲列表两种方式,以及它们的选择依据。深入探讨了并发安全问题的解决方案,如CAS失败重试和本地线程分配缓冲(TLAB)的概念。此外,还介绍了内存初始化、对象头设置和对象初始化的过程。

1  检查加载

2  分配内存,

2.1 划分内存方式(指针碰撞,空闲列表),

指针碰撞,

如果 Java 堆中内存是绝对规整的, 所有用过的内存都放在一边, 空闲的内存放在另一边, 中间放着一个指针作为分界点的指示器, 那所分配内存就仅仅
是把那个指针向空闲空间那边挪动一段与对象大小相等的距离, 这种分配方式称为“指针碰撞”

空闲列表
如果 Java 堆中的内存并不是规整的, 已使用的内存和空闲的内存相互交错, 那就没有办法简单地进行指针碰撞了, 虚拟机就必须维护一个列表, 记录上哪些内存块是可用的, 在分配的时候从列表中找到一块足够大的空间划分给对象实例, 并更新列表上的记录, 这种分配方式称为“空闲列表” 。

选择哪种分配方式由 Java 堆是否规整决定, 而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
如果是 Serial、 ParNew 等带有压缩的整理的垃圾回收器的话, 系统采用的是指针碰撞, 既简单又高效.

如果是使用 CMS 这种不带压缩(整理) 的垃圾回收器的话, 理论上只能采用较复杂的空闲列表
 

2.2 解决并发安全  (CAS 失败加重试,本地线程分配缓冲)

CAS 机制
解决这个问题有两种方案, 一种是对分配内存空间的动作进行同步处理——实际上虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性

分配缓冲
另一种是把内存分配的动作按照线程划分在不同的空间之中进行, 即每个线程在 Java 堆中预先分配一小块私有内存, 也就是本地线程分配缓冲(ThreadLocal Allocation Buffer,TLAB) , JVM 在线程初始化时, 同时也会申请一块指定大小的内存, 只给当前线程使用, 这样每个线程都单独拥有一个 Buffer, 如果需要分配内存, 就在自己的 Buffer 上分配, 这样就不存在竞争的情况, 可以大大提升分配效率, 当 Buffer 容量不够的时候, 再重新从 Eden 区域申请一块继续使用。
TLAB 的目的是在为新对象分配内存空间时, 让每个 Java 应用线程能在使用自己专属的分配指针来分配空间, 减少同步开销。
TLAB 只是让每个线程有私有的分配指针, 但底下存对象的内存空间还是给所有线程访问的, 只是其它线程无法在这个区域分配而已。当一个 TLAB 用满(分配指针 top 撞上分配极限 end 了) , 就新申请一个 TLAB。

3  内存空间初始化    '零'值

4  设置对象头

5 对象初始化,构造对象


 

### JVM 中对象创建过程Java 程序通过 `new` 关键字来创建一个新的对象实例时,JVM 需要经历一系列复杂的操作才能使这个对象成为可使用的状态。具体来说: #### 1. 类型检查与加载 在执行任何其他动作之前,JVM 必须先确认该类型的定义已经被加载到了内存中。如果尚未加载,则会触发类加载器去查找并加载相应的 `.class` 文件。 #### 2. 内存分配 一旦类型信息被成功加载,下一步就是为新对象分配足够的连续空间用于存储其成员变量和其他必要的元数据。这部分工作通常由垃圾回收器管理的堆区负责完成[^1]。 ```java // 假设有一个简单的Person类 public class Person { private String name; } ``` 对于上述代码中的 `Person` 类,在创建新的 `Person` 实例时,JVM 将为其分配适当大小的空间以容纳 `name` 字段以及其他潜在的数据结构。 #### 3. 构造函数调用前的状态设置 此时虽然已经预留好了物理上的位置给即将诞生的新实体,但从逻辑层面看它仍然是未初始化的状态;即所有字段均保持默认值(如数值型为0,布尔型为false,引用类型则为空null),直到显式的赋值行为发生为止[^3]。 #### 4. 执行 `<init>` 方法 紧接着便是最重要的一步—-构造方法(`<init>`) 的实际调用了。这期间可能会涉及多个层次的操作: - 如果存在父类的话,那么子类构造器内部隐含着对其超类构造器的一次自动调用; - 用户自定义的一些初始化语句也会被执行; - 同样地还有静态/非静态初始块内的表达式求值等。 值得注意的是,只有当整个链路顺利完成之后,才会返回指向新建好且已配置完毕的对象引用给原始发起者。 ```java // 定义带参数构造器的例子 public class Employee extends Person{ public Employee(String n){ super(); // 调用父类无参构造器 this.name=n; // 设置员工姓名 } } ``` 在这个例子中,当我们尝试构建一个具体的 `Employee` 对象时,除了自身的初始化外还会依次向上追溯至最顶层基类 `Object` 来确保每一个环节都被妥善处理过。 #### 5. 返回对象引用 最后,经过前面几步精心打造出来的成品会被封装成一个有效的引用传递回最初发出请求的地方供后续使用。 综上所述,尽管表面上看起来只是简单的一个 `new` 操作符的应用,背后却隐藏着许多细致入微的工作流程保障了最终产物的质量和安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值