首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

HotSpot虚拟机对象详解

  • 25-04-20 00:20
  • 4175
  • 10471
juejin.cn

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. 执行构造方法()

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

二、对象内存布局

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)影响:

      • 相同宽度的字段分配在一起(如long和double优先,int和float次之)。
      • 父类字段出现在子类之前(-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
注:本文转载自juejin.cn的敖正炀的文章"https://juejin.cn/post/7486877388047925284"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

143
阅读
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top