深入理解Java虚拟机(三):JAVA内存管理机制-HotSpot对象探秘

在了解了JAVA的内存分配之后,需要进一步了解虚拟机的其他细节,例如:对象是如何创建的,对象是如何布局的。

参考链接:
https://blog.csdn.net/qq_38386316/article/details/81903006
https://blog.csdn.net/itmyhome1990/article/details/78764089

对象的创建

java是面向对象的语言,因此对象的创建无时无刻都存在。在语言层面,使用new关键字即可创建出一个对象。但是在虚拟机中,对象创建的创建过程则是比较复杂的。


当虚拟机遇到一条new指令时,开始进行对象的创建:

  • 将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用。

    • 若没有,说明这个类还没有被定义,会抛出ClassNotFoundException.
    • 若有,则继续下一步。
  • 检查这个符号引用代表的类是否已被加载、解析、和初始化过

    • 如果没有,那必须先执行类加载的过程。
    • 在类加载通过后,接下来虚拟机将为新生对象分配内存。
  • 根据方法区中该类的信息确定所需的内存大小,

    • 对象所需内存的大小在类加载完成后,便可确定。且一个类所产生的所有对象的内存大小是一样的,Jvm在一个类被加载进方法区的时候就知道该类生产的每一个对象所需要的内存大小。
  • 把一块确定大小的内存从Java堆中划分出来给新的对象

  • 分配堆中内存的两种方式:

    • 指针碰撞:如果jvm的垃圾收集器采用复制算法或标记-整理算法,那么堆中空闲内存是完整的区域,并且空闲内存和已使用内存之间由一个指针标记。那么当为一个对象分配内存时,只需移动指针即可(指针向空间那边挪动一段与对象大小相等的距离)。
    • 空闲列表: 如果jvm的垃圾收集器采用标记-清除算法,那么堆中空闲区域交错,因此,虚拟机需要维护一个列表记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
  • 但是内存的分配是同步的,如果一个线程刚分配一个对象内存,但是还没有修改指针所指向的位置,那么另一个线程分配对象的时候可能就出错了。解决方法有两个,一是对分配内存空间的动作进行同步处理(CAS方式)。另一种是把内存分配的动作按照线程划分在不同的空间进行,每个线程在java堆中预分配一小块内存,称为本地线程分配缓冲(TLAB)。只有TLAB用完并分配新的TLAB时,才需要同步。JVM是否开启TLAB功能,可通过-XX:+/-UseTLAB参数来设定。

  • 内存分配完之后,初始化零值(不包括对象头),如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。

  • 接下来,JVM对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中,根据JVM当前运行状态不同,如是否启用偏向锁等,对象头会有不同的设置方式。

  • 执行完new指令后接着执行方法,把对象按照程序员的意愿进行初始化,这样一个对象就初始化完成了。


对象的内存布局

对象在内存中存储的布局可以分为3块区域:

  • 对象头(Header,包括两部分)
    • 第一部分用于存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的存储官方称为Mark Word)在这里插入图片描述
    • 类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个java数组,那在对象头中还必须有一块用于记录数组长度的数据。因为虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是从数组的元数据中无法确定数组 的大小。
  • 实例数据(Instance Data)
    • 实例数据部分是对象真正存储的有效信息,也是在程序代码中多定义的各类型的字段内容。它就是成员变量的值,其中包含父类的成员变量和奔雷的成员变量。
  • 对齐填充(Padding)。
    • 仅仅起着占位符的作用,由于HotSpot要求对象的总长度必须是8字节的整数倍。由于对象头一定是8字节的整数倍,但实例数据部分的长度是任意的,因此需要对齐填充字段来补全,确保整个对象的长度为8的整数倍。
对象的访问定位

在上述中new出对象之后,我们需要进行访问,我们的Java程序需要通过栈上的Reference数据来操作堆上的具体对象。Reference访问对象的方式目前主流的有两种:

  • 句柄访问
  • 直接指针访问。

如果直接使用句柄访问,java堆中将会划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象数据与类型数据各自的具体地址信息,如下图所示。
在这里插入图片描述如果使用直接指针访问,那么java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址,如下图所示。
在这里插入图片描述

这两种对象访问方式各有优势
  • 使用句柄来访问的最大好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。

  • 使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销。HotSpot虚拟机使用的是直接指针访问的方式。句柄来访问的情况也十分常见。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值