【详细图解】一个 Java 对象是如何被创建的?

程序员日常最喜欢的自我调侃就是,没有对象很简单啊,自己写代码 new 一个不就好了,说到这里,我们的骄傲就已经尽数体现了。

不过话说回来,一个简简单单的 new 背后,JVM 到底为我们做了些什么呢, Java 的对象具体是怎么被创建的呢,一起来看看吧。

比如我们想要创建一个 Person 对象:

Person person = new Person();

在 JVM 中的加载流程如下图所示:

Step1:类加载检查

JVM 虚拟机遇到⼀条 new 指令时,⾸先会检查在常量池中是否可以定位到这个类的符号引⽤(比如 org.simple.Person),并且检查这个符号引⽤代表的类是否已被加载、解析和初始化过。

如果没有,那必须先执⾏相应的类加载过程。

Step2:分配内存

当类加载完成后,就可以确定对象所需的内存大小了。

为对象分配空间的任务实际上就等同于把⼀块确定大小的内存从 Java 堆中划分出来。

Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。

这⼀步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使⽤,也就是说,程序能访问到这些字段的数据类型所对应的零值。

Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进⾏必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息,这些信息都存放在对象头中。

另外,根据虚拟机当前运⾏状态的不同,如是否启⽤偏向锁等,对象头会有不同的设置⽅式。

Step5:执行 init 方法

此时从虚拟机的视⻆来看,⼀个新的对象已经产⽣了,但从 Java 程序的视⻆来看,对象创建才刚开始, ⽅法还没有执⾏,所有的字段都还为零。

所以⼀般来说,执⾏ new 指令之后会接着执⾏初始化方法,把对象按照程序员的意愿进⾏初始化,这样⼀个真正可⽤的对象才算完全创建成功。


以上就是对象的创建过程了,其实还有很多细节是我们需要进一步了解的,比如,Java 是如何为一个新的对象分配内存的呢?

内存分配⽅式有 “指针碰撞” 和 “空闲列表” 两种,选择哪⼀种方式进行分配,主要取决于 Java 堆内存是否规整,比如 "标记-整理" 算法、复制算法就属于规整类算法,而 "标记-清除" 则不属于。

1. 指针碰撞法

假设 Java 堆中内存是规整的,已分配的内存和空闲内存分别在不同的一侧,通过一个指针作为分界点,需要分配内存时,仅仅需要把指针往空闲的一端移动与对象大小相等的距离。

2. 空闲列表法

如果 Java 堆的内存不是规整的,已分配的内存和空闲内存相互交错。那么 JVM 通过维护一个列表来记录可用的内存块信息。

当分配操作发生时,从列表中找到一个足够大的内存块分配给对象实例,并更新列表上的记录。


此外,在创建对象的时候还有⼀个很重要的问题,就是线程安全。

在实际开发过程中,创建对象是很频繁的事情,所以作为虚拟机必须要保证线程安全。

通常采⽤两种⽅式来保证线程安全:

CAS + 失败重试: CAS 是乐观锁的⼀种实现⽅式,虚拟机采⽤ CAS + 失败重试的方式保证更新操作的原⼦性。

TLAB(线程本地分配缓存区): 预先为每⼀个线程在 Eden 区分配⼀块内存,JVM 在给线程中的对象分配内存时,⾸先在 TLAB 分配,当对象⼤于 TLAB 中的剩余内存或 TLAB 的内存已⽤尽时,再采⽤上述的 CAS 进⾏内存分配。


本篇博客参考了 JavaGuide 面试题中的内容,感谢原作者,送上花花~

原始链接:https://github.com/Snailclimb/JavaGuide

Guess you like

Origin blog.csdn.net/j1231230/article/details/118927021