Java 对象的创建过程

本贴最后更新于 1180 天前,其中的信息可能已经时移俗易

概述

在之前的一篇文章《关于 java 继承的哪些事》简单讲了 Java 创建对象的过程,但具体细节当时并没有详细讲。因而本篇文章以 HotSpot 虚拟机为例,来讲一下 Java 虚拟机是如何创建一个对象的?

简单来说 Java 对象的创建过程总共分为 5 步:

image.png

Java 创建对象的过程

类加载检查

首先当虚拟机遇到一个 new 指令时,首先会去检查这个参数能否在常量池中定位到这个类的符号引用,并且检查这个符号引用所代表的的类是否已经被加载、连接、解析和初始化过,如果没有需要先执行类的加载操作(详细过程可参考"类的加载流程" )。

分配内存

当类加载检查通过后,接下来虚拟机需要为新生对象分配内存,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配内存的方式主要有两种:指针碰撞空闲列表

具体选择哪种方式取决于 Java 堆是否规整。而 Java 堆是否规整取决于垃圾收集器所采用的垃圾回收算法是否具有 空间压缩整理 的能力。

image.png

具体来说,指针碰撞分配内存空间的过程如下:

在 Java 堆规整的情况下,所有被使用过的内存放到一边,所有未被使用过的内存放置到另一边,中间放置一个指针作为分界点的指示器,当需要分配内存空间时,只需要将空闲指针向空闲内存方向移动对象内存大小的位置即可。

该算法能够使用的前提必须是空间是规整的,因为如果空间是碎片化的,很明显该算法就会失效。

另一种算法是空闲列表,其分配内存的过程如下:

虚拟机会维护一个列表,该列表中会记录那些内存块是可用的,在分配内存时,会在空闲列表中找到一块足够大的内存块来给对象实例,最后更新列表记录

当然该算法也有不足,由于需要时刻维护一个空闲列表,因而会增加空间和时间开销,但优点就是它可以用来对离散的空间进行内存分配。

最后在分配内存时,可能也会存在并发安全的问题,为了解决该问题,虚拟机采用两种方式来解决:

  • CAS+ 失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配

初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置 ,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

执行 init()方法

上边一些列工作完成之后,从虚拟机角度来看,实际上一个对象已经产生了。但从 Java 程序的角度来看,对象创建才刚刚开始---构造函数,即 Class 文件中的 <init>() 方法还没有执行所有字段还都是零值。并没有按照构造方法来对对象进行初始化,因而最后一步需要执行 <init>() 按照程序员的意愿来对代码进行初始化。

好了,前边对象的创建过程,我们已经讲清楚了,那有了对象之后,如何定位对象到对象进行使用呢?

对象的访问定位

建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有 ① 使用句柄② 直接指针 两种:

句柄: 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;

image.png

直接指针:如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。具体寻址过程如下图所示:

image.png

这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

总结

本文主要讲了对象创建的五大过程以及每一步的具体作用,后续为了使用 Java 对象因而讲了一些对象访问定位相关的知识。

参考

  1. Java 内存区域
  2. 《深入理解 JVM 虚拟机》
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3169 引用 • 8208 回帖
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...