深入理解java虚拟机 第2章(二):对象创建,内存布局,访问

时间:2023-01-02 14:00:44

java对象的创建

java对象创建,在语法层面上只是一个简单的new关键字,但是实际的内存执行过程是如何,具体的步骤又是怎样的流程?

1,虚拟机的类加载检查

jvm在遇到new指令时,会先去检查new指令的参数是否能在常量池中定位到参数代表的类的符号引用,并检查该类是否被加载,解析和初始化。如果没有,则先进行相应的类的初始化过程。

2,为新生的对象分配内存。

对象所需的内存大小在类加载完成后就已经确定,接下来就是java堆如何为对象实例分配内存(前面讲过java堆的主要目的就是为对象实例分配内存空间),java堆为对象分配内存的方式有两种:

  • 指针碰撞
    假设 java堆内存是规整的,java堆中已使用和未使用的内存分别放在不同的空间,使用一个指针作为两者之间分界点的指示器,每次为对象实例分配内存的过程就是指针向空闲空间移动与对象实例大小相等的距离

深入理解java虚拟机 第2章(二):对象创建,内存布局,访问

  • 空闲列表(Free List)

java堆不连续,虚拟机维护一个列表,记录可用的内存块,分配时从列表中寻找一块足够大的空间划分给对象实例,并更新列表的记录。

深入理解java虚拟机 第2章(二):对象创建,内存布局,访问

具体采用哪种分配方式,由gc是否带有压缩整理功能所决定。

对于指针碰撞的方式,注意多线程问题,并发情况下,移动指针不是线程安全的。
实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性

3,虚拟机将分配到的内存空间都初始化为零值

4,虚拟机对对象进行必要设置
对象头中的信息进行设置

5,执行init方法,产生真正可用的对象

创建出新的对象,并设置零值之后,需要进行对象的init操作,将对象按照程序的意愿,进行初始化。

对象的内存布局

HotSpot中,对象在内存中的存储布局分3块:对象头,实例数据,对其填充

对象头包含两部分信息:
1,Mark Word
用于存储对象自身的运行时数据,如hashcode,GC分代年龄,锁状态信息,线程持有的锁等,mark word是非固定的数据结构。

对象头的数据长度分别为32bit或者64bit

2,类型指针
对象指向他的类元数据,虚拟机通过这个指针来确定该对象到底是属于哪个类的实例。并非所有jvm都有该指针,即查找该对象的元数据并非都要经过对象本身。

实例数据
存储对象真正的有效信息,也是在代码中定义的各种类型的字段内容。

对其填充
只是作为占位符作用,当实例数据部分没有对齐,就需要填充来补全。(jvm要求对象大小是8字节的整数倍,对象头正好是8字节整数倍)

对象访问

通过栈上的reference数据来操作堆上的具体对象。具体的引用方式需要jvm厂商自行决定。
主流方式有两种:使用句柄直接指针

  • 句柄访问:
    java堆中单独划分一块内存,作为句柄池,java栈中的对象reference中存放的就是对象的句柄地址,在句柄池中的句柄,包含了两部分:对象的实例数据地址和类型数据地址。

深入理解java虚拟机 第2章(二):对象创建,内存布局,访问


  • 直接指针访问
    在java堆中对象布局中包含了类型数据的指针,reference中存储的直接是对象地址。

HotSpot中,对象内存布局:对象头(类型指针),实例数据,对其填充
深入理解java虚拟机 第2章(二):对象创建,内存布局,访问

两者比较:
句柄方式 :java堆中gc回收内存,对象实例数据的地址发生变动,只改变句柄中实例数据指针,而reference的对象句柄地址不需要改动

直接地址方式:节省了一次句柄地址定位的时间开销,速度更快,缺点就是reference地址变动(HotSpot采用该方式)。