深入理解 Java 虚拟机-02 对象

深入理解 Java 虚拟机-02 对象

对象的组成

在 HotSpot 虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

对象头中存储的是与对象自身定义的数据无关的信息,包括:对象自身运行时数据和类型指针两部分。

对象运行时数据如哈希码、GC 分代年龄、锁状态标志、线程持有锁、偏向线程ID 等,这部分在 32 位虚拟机和 64 位虚拟机分别占用 32 比特 和 64 比特,官方的称呼为 "Mark Word"。**Mark Word 被设计成一个动态定义的数据结构,根据对象的状态复用自己的空间。**例如在 32 位的 HotSpot 虚拟机中,如对象未被同步锁锁定的状态 下,Mark Word的 3 2个比特存储空间中的 25 个比特用于存储对象哈希码,4 个比特用于存储对象分代年龄,2 个比特用于存储锁标志位,1 个比特固定为 0,在其他状态(轻量级锁定、重量级锁定、GC标 记、可偏向)下对象的存储内容如表所示。

**类型指针(Klass Pointer)即对象指向它的类型元数据的指针,Java 虚拟机通过这个指针来确定该对象是哪个类的实例。**不过并不是所有的虚拟机实现都需要在对象数据上保留类型指针,例如通过句柄访问对象。

cpp 复制代码
// Bit-format of an object header (most significant first, big endian layout below):
//
//  32 bits:
//  --------
//  hash:25 ------------>| age:4    
biased_lock:1 lock:2 (normal object)
//  JavaThread*:23 epoch:2 age:4    
biased_lock:1 lock:2 (biased object)
//  size:32 ------------------------------------------>| (CMS free block)
//  PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

除去对象头,接下来的实例数据部分是对象真正存储的有效信息,包括继承以及自定义的字段,这部分的存储顺序会受到虚拟机分配策略参数(-XX:FieldsAllocationStyle 参数)和字段在 Java 源码中定义顺序的影响。默认规则为:longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPs),在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果 HotSpot 虚拟机的 +XX:CompactFields 参数值为 true(默认就为true),那子类之中较窄的变量也允许插入父类变量的空隙之中,以节省出一点点空间。

对象的第三部分是对齐填充,这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于 HotSpot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是任何对象的大小都必须是 8 字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数,因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

对象的创建

当在程序中执行一个 new 指令创建普通 Java 对象(不包括数组和 Class 对象)时,JVM 首先将去检查这个指令的参数是否能在常量池中定位到 一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

在类加载检查通过后,接下来虚拟机将为新生对象分配内存,内存的大小通过上一部分中提到的方式已经确定下来了,所以分配内存就是从 Java 堆中划分出来一个这样大小的内存块。

假如堆内存非常规整,一侧全都是使用过的内存,另一侧则是空闲内存,那么只需维护一个指针,在需要分配内存时指针向空闲侧移动即可,这种方法被称为指针碰撞;而如果堆内存并不规整,那么则需要使用"空闲列表"维护当前空闲的内存空间。堆内存非常规整取决于使用的垃圾回收算法是否带有空间整理(Compact),如果只是标记清除例如 CMS,理论上(因为CMS在分配内存时会分配一个比所需内存更大的缓冲区,这样后续依然可以在这块缓冲区内使用指针碰撞)就只能使用空闲列表法,而如果是 ParNew 这类带有整理过程的收集器,则可以使用指针碰撞法。

分配内存在并发情况下不是线程安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。一种解决方案是采用 CAS 重试直到分配成功,另一种方案则是把内存分配的动作按照线程划分在不同的空间之中进 行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓冲区时才需要锁定。

内存分配完成后,会首先对这块内存区域赋零值,并设置相应的对象头等信息,此时在虚拟机的角度一个新的对象已经产生了,但是在用户的角度,这个对象还需要执行 class 的 <init> 方法,按照程序员的意愿对对象进行初始化,这样一个真正可用的对象才算完全被构造出来。

java 复制代码
class User {
    private int age = 3;
}

在内存分配完成时,user 对象的 age 属性其实是 int 的零值 0,在执行完类的 init 方法后, age 的属性才是我们定义的 3。

对象的访问定位

Java 程序会通过栈上的 reference 数据来操作堆上的具体对象,reference 的理解是如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该 reference 数据是代表某块内存、某个对象的引用。

但是 reference 只规范了能够找到对象,而没有规定其实现方式,所以主流的实现方式其实有使用句柄和直接访问两种。

使用句柄的好处是,在垃圾回收的过程中,对象的内存位置是会变化的,这时只需要修改句柄引用的对象位置即可,缺点则是每次访问对象都多一次内存访问。

使用直接访问的好处是可以直接访问到对象实例数据(注意是对象实例数据,访问对象的类型数据时需要考虑如何实现),但在垃圾回收的过程中可能需要更新多处引用对象的值。

对 HotSpot 而言,它主要使用直接访问方式进行对象访问, 现代的 HotSpot JVM 使用了分代 GC,大部分对象都是在年轻代分配并快速回收, 这些对象基本不会移动。

对象大小与压缩指针

在 64 位系统中,普通的对象引用通常需要 64 位(8字节)的空间,因为 4 字节只能表示 32 位的一个地址,也就是最多只能表示 4GB 的空间,但是绝大多数的应用程序都不会占用那么大的空间,即堆内存一般不会超过 32GB。

Java 的对象通过设计,大小一定是 8 的倍数,那么也就是对象所在位置的值的 2 进制最后 3 位一定是 0,既然我们知道对象的最后 3 位一定是 0,就可以不要这 3 位了,也就是之前的 0001 代表的是第 1 个字节,而现在的 0001 代表的是第 8 个字节( 0001 << 3),那么可以表示的内存范围也是之前的 8 倍,即可以表示 32GB 的空间。

64 位 Java 虚拟机引入了压缩指针(Compressed Pointer)技术,将Java对象指针压缩为 32位。这样,对象头部中的Klass Pointer 也被压缩为 32 位,从而将对象头部的大小从 16 字节减小到 12 字节。

​ 图示来自:压缩指针对对象大小的影响

在存储的时候,由于已经 8 字节对齐,所以我们直接 >> 3 存储,在读取时,我们需要用一个 long 类型接收读取值 << 3 位后的结果 + heapBase(堆起始地址)。

目前压缩指针在 64 位 JVM && 堆内存 ≤ 32GB 时是默认开启的~

相关推荐
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-数据库设计核心业务方案(微调)
java·数据库·人工智能·spring boot·领域驱动
yangminlei2 小时前
Spring Boot 3 + Spring AI 实战:十分钟集成 OpenAI API 构建智能应用
java·openvino
是三好2 小时前
java集合
java·开发语言
weixin_BYSJ19872 小时前
django农作物批发交易系统--附源码24008
java·javascript·spring boot·python·django·flask·php
Knight_AL2 小时前
Java + FFmpeg 实现视频分片合并(生成 list.txt 自动合并)
java·ffmpeg·音视频
空空kkk2 小时前
SpringBoot整合Thymeleaf
java·spring boot·spring
计算机毕业设计开发2 小时前
django高校公寓管理系统--附源码64226
java·c++·spring boot·python·spring cloud·django·php
季明洵2 小时前
Java中哈希
java·算法·哈希
组合缺一2 小时前
Claude Code Agent Skills vs. Solon AI Skills:从工具增强到框架规范的深度对齐
java·人工智能·python·开源·solon·skills