Java对象的创建及布局

对象的创建

在Java虚拟机中当遇到一条字节码的new指令时

1.检查类是否被加载

首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,那就必须先执行相应的类加载过程。

2.分配内存空间

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配内存空间的任务实际上便等同于把一块确定大小的内存块从Java堆中划分出来

  • (空间分配管理有以下两种算法 1.指针碰撞法 2.空闲列表法)。

同时对象在堆空间中划分区域的过程涉及到并发问题。一共有两种解决方法

  • 对分配内存空间的动作进行同步处理——实际上采用CAS配上失败重试的方式来保证更新操作的原子性
  • 使用TLAB本地分配缓冲在线程本地缓冲区中分配

3.赋零值

内存分配完成后,虚拟机必须将分配到的内存空间(不包括对象头)都初始化为零值,如果使用了TLAB的话,这一项工作也可以提前到TLAB分配时顺便进行。
这就保证了对象的实例字段在Java代码中不赋初值就可以直接使用,程序可以访问到零值而不会出错

4.对象头设置

接下来虚拟机还要对对象头进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的Hash码(实际上只有在调用Object::hashCode()方法时才会计算)、GC分代年龄信息、锁标记位、偏向状态等信息,这些信息会被放在对象的对象头中。

5.赋初始值

在上面的步骤完成后,从虚拟机的视角看来,一个新的对象已经产生了(均为零值的对象)但是从Java程序的角度来看,对象的创建才刚刚开始——构造函数,即Class文件中的《init》方法还没有执行、所有的字段都为默认的零值,对象需要的其他资源和状态信息也还没有按照预定的意图构造好
一般来说,new指令之后会接着执行《init》方法,按照程序员的意愿对对象进行初始化,这样一个真正可用的对象才算完全被构造出来。

对象的内存布局

在Java中,对象的堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

对象头

对象头中包含两类信息

  • 用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁标志位、线程持有的锁、偏向时间ID、偏向时间戳等。在32位的虚拟机中为32个比特、在64位的虚拟机中为64个比特
    由于对象头中的信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成动态定义的数据结构(在不同状态,每一位的含义不同)
  • 类型指针
    对象头的另一部分是类型指针,即对象指它的类型元数据的指针
    Java虚拟机通过这个指针来确定该对象是哪个类的实例,(不是所有虚拟机都采用这种方式,共有两种 1.句柄访问 2.直接指针访问)
  • 数组类型需要额外记录数组长度
    如果对象时一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是如果数组的长度不是确定的,将无法通过元数据中的信息推断出数组的大小,即数组大小 = 对象大小 * 数组长度

实例数据部分

实例数据部分存储的是对象真正存储的有效信息,即我们程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。父类子类中的属性中,相同宽度的字段总是被分配到一起存放,在满足这个前提的情况下,在父类中定义的变量会出现在子类之前。

对齐填充

由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头被精心设计为8字节的整数倍,因此如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全凑成8字节的整数倍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值