Java内存区域与对象结构探秘--HotSpot虚拟机对象

概述:以最常用的虚拟机HotSpot和最常用的内存区域Java堆为例,深入探讨HotSpot虚拟机在Java堆中对象分配、布局和访问的全过程。

1 对象的创建
  • 类加载检查
    • 当JVM遇到一条new指令时,首先检查常量池中是否有这个类的符号引用,并且检查这个类是否已经被加载、解析和初始化过。如果没有,先执行类加载过程。
  • 内存分配
    • 类加载完成后,JVM为对象分配内存。
      • 对象所需内存的大小在类加载后便完全确定。
    • 内存分配方法取决于Java堆的情况:
      • 指针碰撞(Bump The Pointer):如果堆内存是规整的,分配仅需移动指针(该指针分隔开了占用内存与空闲内存)。
      • 空闲列表(Free List):如果堆内存是不规整的,JVM则需要维护一个记录可用内存块的列表。
      • java堆是否规整由所采用的垃圾收集器是否带有空间压缩整理(Compact)的能力决定。
  • 内存分配中的线程安全
    • 由于对象频繁创建,内存分配需要处理线程安全。解决方案包括:
      • 使用CAS(Compare And Swap)配上失败重试的方式保证更新操作的原子性。
      • 使用本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),即每个线程预分配一小块内存,只有分配新的缓冲区时才需要同步锁定。
  • 内存初始化
    • 分配的内存(除对象头外)需初始化为零值,确保字段默认值正确,如使用TLAB则可以将此步骤提前至分配缓冲区时执行。
  • 对象头设置
    • 设置对象的元数据信息,如类的类型、哈希码、GC信息等。
  • 执行<init>方法:
    • JVM视角上对象已创建,但从Java程序的视角来看,对象的构造函数(即Class文件中的()方法)还需执行,完成字段初始化和其他资源的设置。
2 对象的内存布局

三个主要部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

  • 对象头(Object Header):

    • Mark Word:存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志等。这部分数据的长度取决于系统是32位还是64位。Mark Word有着动态定义的数据结构,会根据对象的状态(如是否被锁定)而有所不同。

      在这里插入图片描述

    • 类型指针:指向类元数据的指针,用于确定对象是哪个类的实例。查找对象的元数据信息并不一定要经过对象本身(见2.2.3)。

    • 数组长度:如果对象是数组,那么对象头还会包含数组长度信息,因为当数组的长度不固定时,无法通过类的元数据推断出数组的大小。

  • 实例数据(Instance Data):

    • 存储对象的实际有效信息,包括类中定义的各种字段内容,无论是继承的还是在类中直接定义的。
    • 实例数据的存储顺序受到JVM分配策略和字段在Java源码中的定义顺序的影响
      • 相同宽度的字段总是被分配到一起存放,在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。
  • 对齐填充(Padding):

    • 不是必须的,只用于填充,使得对象的总大小为8字节的整数倍
      • 原因:HotSpot虚拟机要求对象的起始地址必须是8字节的整数倍,而对象头已被精心设计为8字节的倍数(1倍或者 2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
3 对象的访问定位

Java程序会通过栈上的reference数据来操作堆上的具体对象,而具体的访问方式并没有在虚拟机规范中详细定义,主流的实现有句柄和直接指针两种。

  • 使用句柄访问

    在这里插入图片描述

    • Java堆可能划分出一部分空间作为句柄池。

    • 引用(reference)中存储的是对应句柄的地址。

    • 句柄包含了对象实例数据和类型数据的指针。因此,访问对象需要两步:首先通过引用找到句柄,然后通过句柄找到实际的对象数据。

    • 句柄访问的优势在于,当对象被移动(比如在GC过程中)时,只需要修改句柄中的对象实例指针,而引用本身不需要改变,这为对象移动提供了便利。

  • 使用直接指针访问

    在这里插入图片描述

    • 引用(reference)直接指向对象在堆中的地址。
    • 访问对象时,不需要像使用句柄那样额外的定位句柄,从而提高了访问速度。
    • 直接指针访问的优势在于访问速度更快,因为节省了一次指针定位的时间。由于在Java中对象访问非常频繁,这种方式可以显著节省执行成本
    • HotSpot虚拟机主要采用这种方式进行对象访问,尽管在某些情况(如使用特定的垃圾收集器)可能会有变化(见第3章)。
  • 对象或句柄中的类型数据指针与Class对象区别:

    • 对象或句柄中的类型指针:这是为了JVM自身的运行时需求而存在的。这些指针直接指向方法区(或Java 8及更高版本中的元空间Metaspace)中的类元数据。
    • 堆内存中的Class对象:这是供程序员在Java代码中使用的,主要用于反射操作。通过Class对象,程序员可以获取类的信息(如类名、方法、字段等),创建类的实例,甚至在运行时修改类的结构。Class对象本身也包含指向方法区中类元数据的引用,从而使得Java程序能够反射地访问和操作类的信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值