HotSpot虚拟机对象详解

HotSpot虚拟机对象详解

HotSpot虚拟机对象创建需经类加载、内存分配(指针碰撞/空闲列表)、初始化及构造方法;内存布局包含对象头(Mark Word、类型指针)、实例数据与对齐填充;访问采用直接指针,优化性能。

一、对象创建过程

HotSpot虚拟机中,对象的创建遵循以下步骤:

  1. 类加载检查

    • 当遇到new指令时,首先检查该指令的参数(类符号引用)是否能在常量池中定位到对应的类。
    • 检查该类是否已被加载、解析和初始化。若未加载,则先执行类加载过程。
  2. 内存分配

    • 虚拟机为新生对象分配内存。分配方式取决于堆内存是否规整:

      • 指针碰撞(Bump the Pointer) :适用于堆内存规整(如使用Serial、ParNew等带压缩整理的收集器)。通过移动指针来分配内存。
      • 空闲列表(Free List) :适用于堆内存不规整(如使用CMS收集器)。维护一个列表记录可用内存块。
    • 并发处理:为避免多个线程分配内存冲突,采用两种策略:

      • CAS(Compare And Swap)重试:通过原子操作保证指针更新的正确性。
      • TLAB(Thread Local Allocation Buffer) :为每个线程预先分配一小块内存(-XX:+UseTLAB),线程私有,避免竞争。
  3. 内存空间初始化

    • 将分配的内存空间初始化为零值(不包括对象头)。确保实例字段在不赋初值时可直接使用(如int默认0)。
  4. 设置对象头(Object Header)

    • 对象头包含两类信息:

      • Mark Word:存储对象自身运行时数据:

        • 哈希码(Hash Code)
        • GC分代年龄(Generational Age)
        • 锁状态标志(如偏向锁、轻量级锁、重量级锁)
        • 线程持有的锁、偏向线程ID、偏向时间戳等
      • 类型指针(Class Pointer) :指向方法区的类元数据,确定对象属于哪个类。

    • 若启用压缩指针(-XX:+UseCompressedOops),类型指针占用4字节(否则8字节)。

  5. 执行构造方法()

    • 调用对象的构造函数(即<init>方法),按程序员的意愿初始化对象字段。
    • 此时对象才完全成为业务逻辑中的有效实例。

二、对象内存布局

HotSpot对象在堆内存中的存储布局分为三部分:

  1. 对象头(Header)

    • Mark Word(8/16字节):

      • 32位虚拟机:4字节;64位虚拟机:8字节(开启压缩指针则为4字节)。

      • 内容随锁状态变化,例如:

        锁状态 存储内容
        无锁 哈希码、分代年龄、偏向模式0
        偏向锁 偏向线程ID、时间戳、分代年龄、偏向模式1
        轻量级锁 指向栈中锁记录的指针
        重量级锁 指向互斥量(Monitor)的指针
        GC标记 空(用于GC标记阶段)
    • 类型指针(4/8字节):指向方法区的类元数据,用于确定对象类型。

    • 数组长度(可选):若对象是数组,额外4字节记录数组长度。

  2. 实例数据(Instance Data)

    • 对象真正存储的有效信息,即类中定义的各种字段内容(包括继承的父类字段)。

    • 字段存储顺序受虚拟机分配策略(FieldsAllocationStyle)影响:

      • 相同宽度的字段分配在一起(如longdouble优先,intfloat次之)。
      • 父类字段出现在子类之前(-XX:CompactFields=true时,允许子类较窄字段插入父类空隙)。
  3. 对齐填充(Padding)

    • 起占位作用,确保对象总大小为8字节的整数倍。
    • 原因:HotSpot要求对象起始地址对齐(如8字节对齐),提升内存访问效率。

示例:一个简单对象的内存布局

arduino 复制代码
class Person {
    int age;          // 4字节
    String name;      // 引用类型(压缩后4字节)
}

假设在64位JVM启用压缩指针,对象布局如下:

  • 对象头:Mark Word(8字节) + 类型指针(4字节) = 12字节
  • 实例数据:age(4字节) + name(4字节) = 8字节
  • 对齐填充:填充4字节,总大小为24字节(12+8+4=24,8的倍数)。

三、对象访问定位

Java程序通过栈上的引用(Reference)访问堆中对象,具体方式有两种:

  1. 句柄访问

    • 实现:堆中划分句柄池,每个句柄包含两个指针:

      • 指向对象实例数据(Instance Data)的指针。
      • 指向方法区类型数据(Class Metadata)的指针。
    • 优点:对象移动(如GC)时,只需更新句柄中的指针,无需修改栈中的引用。

    • 缺点:访问需两次指针跳转,性能略低。

  2. 直接指针访问(HotSpot采用方式)

    • 实现:引用直接存储对象地址,对象头中的类型指针指向方法区类元数据。
    • 优点:访问速度更快(减少一次指针定位)。
    • 缺点:对象移动时需更新所有引用(通过GC的移动算法解决,如复制、标记-整理)。

对比示意图

复制代码
句柄访问:
栈引用 → 句柄池 → 实例数据 & 类型数据
​
直接指针访问:
栈引用 → 实例数据(含类型指针) → 类型数据

四、总结

  • 对象创建:需经历类加载检查、内存分配、初始化、设置对象头和执行构造方法。
  • 内存布局:分为对象头(运行时数据和类型指针)、实例数据和对齐填充。
  • 访问定位:HotSpot选择直接指针访问以优化性能,依赖对象头中的类型指针快速定位类元数据。

关键参数与工具

  • -XX:+UseCompressedOops:启用压缩指针(默认开启)。

  • -XX:FieldsAllocationStyle:调整字段分配顺序。

  • JOL(Java Object Layout)工具:分析对象内存布局(示例输出):

    arduino 复制代码
    java -jar jol-cli.jar internals java.lang.String
相关推荐
秋野酱19 分钟前
基于javaweb的SpringBoot高校图书馆座位预约系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
HWL56791 小时前
Express项目解决跨域问题
前端·后端·中间件·node.js·express
-曾牛1 小时前
Spring AI 集成 Mistral AI:构建高效多语言对话助手的实战指南
java·人工智能·后端·spring·microsoft·spring ai
shengjk13 小时前
序列化和反序列化:从理论到实践的全方位指南
java·大数据·开发语言·人工智能·后端·ai编程
hie988944 小时前
使用Spring Boot集成Nacos
java·spring boot·后端
源码方舟4 小时前
基于SpringBoot+Vue的房屋租赁管理系统源码包(完整版)开发实战
vue.js·spring boot·后端
景天科技苑4 小时前
【Rust trait特质】如何在Rust中使用trait特质,全面解析与应用实战
开发语言·后端·rust·trait·rust trait·rust特质
Mikey_n5 小时前
Spring Boot 注解详细解析:解锁高效开发的密钥
java·spring boot·后端
Kookoos6 小时前
【实战】基于 ABP vNext 构建高可用 S7 协议采集平台(西门子 PLC 通信全流程)
后端·物联网·c#·.net
帮帮志6 小时前
vue3与springboot交互-前后分离【完成登陆验证及页面跳转】
spring boot·后端·交互