JVM学习笔记(三)———虚拟机对象

对象的创建

对象创建分为以下几个步骤

1)当JVM遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用(由于不知道所引用类的具体位置,符号引用以一组符号来描述所引用的目标,例如org.xxx.xxx类这种形式),并检查这个符号引用代表的类是否已经被加载、解析和初始化过。
2)如果没有,则先执行对这个类的类加载过程(可以参考类加载机制)。
3)类加载检查通过后,接下来虚拟机将为新生对象分配内存,内存大小在类加载完成后便已经确定,为对象分配空间的任务相当于将对象所需的空间从一块确定大小的内存块中划分出来。
分配方式有两种,分别为指针碰撞(Bump The Pointer)空闲列表(Free List)

  • 指针碰撞是在Java堆内存绝对规整的情况下使用的,即所有正在被使用的内存空间都在一端,所有空闲的内存空间都在另一端,而中间放着一个指针作为分界点指示器,那么新内存的分配实质上就是将这个指针向空闲空间的方向挪动一段与对象大小相同的距离。这种分配方式通常被使用标记-整理算法进行垃圾收集的系统选用。
  • 空闲列表是一个记录哪些内存块是可用的表,在进行内存分配的时候就在该表上找到一块足够大的内存空间划分给对象实例,并对表进行更新。这种分配方式通常被使用标记-清楚算法来进行垃圾收集的系统选用。

对象的创建在虚拟机中是非常频繁的行为,在并发情况下可能会出现线程安全问题,例如,正在为一个对象分配内存时,指针还没来得及修改,另一个对象也申请了内存空间并用原来的指针来分配内存。
解决并发情况下内存分配的线程安全问题有如下两种方法

  • 对分配内存空间的动作进行同步处理。
  • 为每个线程预先划分一块独立的一小块内存,称作本地线程分配缓冲,当线程需要分配新的内存空间时,就在当前线程的分配缓冲中分配,当线程的分配缓冲区不足以满足新的内存申请,则为其分配新的缓存区,分配新的缓存区时进行同步锁定。

4)内存分配完毕后,虚拟机需要对对象进行必要的设置,需要将该对象属于哪个实例、元数据信息地址、对象哈希码、对象的分代年龄等信息存入一块叫对象头的空间中。
5)上述步骤完成后,执行当前对象Class文件中的< init >()构造方法,按照程序员的意愿对对象进行初始化。至此一个可用的对象才算被完全构造出来

对象的内存布局

HotSpot虚拟机中,对象在堆内存中的存储布局可以分为三个部分,分别是对象头、实例数据和对齐填充。

对象头

HotSpot虚拟机对象的对象头包含两类信息

  • 第一类是用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这段数据的长度在32位和64位虚拟机分别对应32比特和64比特。
  • 第二类用于存放类型指针,此指针指向该对象的类型元数据,JVM通过这个指针判断该对象是属于哪个类的实例。如果对象是一个数组,那么对象头中还必须有一块用于记录数组长度的数据。

实例数据

  • 实例数据部分存放的是程序员在程序中定义的字段内容,无论是父类继承还是子类定义的字段都必须记录。
  • 此部分数据的存储顺序可以由虚拟机分配策略参数-XX:FiledsAllocationStyle改变,默认顺序受Java源码定义顺序影响,HotSpot虚拟机中默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops(一般对象指针),由默认顺序不难看出,相同宽度的字段总是被分配到一起存放,除oop类型,其他类型占位由大到小顺序存放。满足上述前提条件的情况下,父类中定义的变量会出现在子类之前,如果虚拟机参数+XX:CompactFields设为true时,子类中较窄的变量也允许插入父类变量的内存空隙中,以此节约一点内存空间。

对齐填充

由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,即任何对象的大小都必须是8的整数倍,对齐填充部分就是为此而存在,当作占位符进行对齐补全,并没有实际意义,也不必然存在。

对象的访问定位

由于reference类型在《Java虚拟机规范》中仅规定了它是一个对象的引用,而没有要求其具体的实现,所有对象访问的方式不固定,具体看虚拟机的实现,主流的对象访问方式有两种

  • 句柄访问:使用此种访问会额外增加一部分内存开销,Java堆中会划分出一部分内存作为句柄池,reference中存放的就是对象的句柄地址,而句柄中包含实际的对象实例数据和类型数据的具体地址信息。示意图如下
    在这里插入图片描述

  • 指针访问:如果使用此种访问方式,reference直接存放就是对象的地址信息。示意图如下
    在这里插入图片描述

两种对象访问方式各有优劣,直接指针访问方式最直观的优势便是访问速度快,因为相比句柄访问,这种方式节省了一次指针定位的时间开销。而句柄访问对象存储地址稳定,当有对象在内存中移动时,只需要更改句柄所存储的地址即可,句柄本身不需要被移动修改,而直接指针需要同步修改reference。


参考书籍 《深入理解Java虚拟机》第三版 ——周志明
本篇内容主要用于作者自身学习总结记录,才疏学浅,如文中出现纰漏,还望指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7rulyL1ar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值