3对象
对象
对象的创建
内存分配方式
- 指针碰撞:假设Java堆中内存绝对规整,所有用过的内存在一边,没有用过的在另一边,中间放着一个指针作为分界器。分配内存为将指针向空闲区域移动与对象大小相等的一段距离。
- 空闲列表:堆中内存不规整,虚拟机需要维护一个列表,记录那些内存块是可用的。在分配的时候从列表中找到一块足够大的内存划分给对象并更新列表上的记录。
实现并发下安全分配内存的方法
- CAS:对分配内存空间的动作进行同步处理——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
- 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在jvm堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocaton Buffer, TLAB)。在TLAB用完重新分配新的TLAB时,才需要同步锁定。
对象的布局
- 对象头(Header) + 实例数据(Instance Data) + 对齐填充(Padding)
对象头
8个字节
每个Java对象都有对象头。如果是非数组类型,则用2个字宽来存储对象头,如果是数组,则会用3个字宽来存储对象头。在32位处理器中,一个字宽是32位;在64位虚拟机中,一个字宽是64位。对象头的内容如下表:
长度 内容 说明 32/64bit Mark Word 存储对象的hashCode或锁信息等 32/64bit Class Metadata Address 存储到对象类型数据的指针 32/64bit Array length 数组的长度(如果是数组)
对象自身的运行时数据(Mark Word)
自身的运行数据包括:哈希码,GC分代年龄(固定4位),锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。此部分数据长度在32和64的虚拟机中分别为32bit和64bit
非固定的数据长度。
锁状态 29 bit 或 61 bit 1 bit 是否是偏向锁? 2 bit 锁标志位 无锁 0 01 偏向锁 线程ID 1 01 轻量级锁 指向栈中锁记录的指针 此时这一位不用于标识偏向锁 00 重量级锁 指向互斥量(重量级锁)的指针 此时这一位不用于标识偏向锁 10 GC标记 此时这一位不用于标识偏向锁 11
如果对象为一个数组的话,对象头还必须有一块用于记录数组长度的数据,四个字节。
ClassPointer指针
- 指向Class的指针,对象指向它的类元数据的指针。
- 默认8个字节,启用指针压缩后,-XX:+UserCompressdClassPoints后占用四个字节。
实例数据
- 对象真正储存的有效信息,代码中所定义的各种类型的字段内容。存储顺序受虚拟机分配策略参数和字段在Java源码中定义顺序的影响。相同宽度的字段被分配到一起,在满足这个前提下,父类定义的变量会出现在子类之前。
- 引用类型:
- 默认8个字节,启用:-XX:+UserCompressedOops后为四个字节
对齐填充
- 不是必然存在,仅仅起着占位符的作用。对象的大小必须是8字节的整数倍。
指针压缩
- 什么是指针压缩?
- 将对象头中的Class Metadata Address 8个字节压缩为4个字节
- 为什么要开启指针压缩
- 8位增加了Gc开销,64位对象引用需要占用更多的堆空间,留给其他数据的空间就会减少,从而加快了GC的发生,更频繁的进行GC
- 降低CPU缓存的命中率:64位对象引用增大了,CPU能缓存的oop将会更少,从而降低了CPU缓存的效率
- 怎么做到的指针压缩
- 因为java对象进行了对齐填充,所有都是8字节划分的,这样就可以存一个映射地址编号
- 限制
- 最大内存是32GB
Object o = new Object()占几个字节?
- 对象头8个字节,ClassPointer指针无压缩8字节,压缩4字节 所以共16个字节
对象的访问定位
- Java程序需要通过栈上的reference数据来操作堆上的具体对象。
- HotSpot使用的是直接指针。
句柄
使用句柄访问对象,会在堆中开辟一块内存作为句柄池,句柄中储存了对象实例数据(属性值结构体)的内存地址,访问类型数据的内存地址(类信息,方法类型信息)。reference中存储的是对象的句柄地址。
好处
- reference中存储的是稳定的句柄地址,当对象被移动时,指挥改变句柄中的实例数据指针,而reference不需要修改
直接指针
reference中存储的直接就是对象地址(对象在堆中的内存地址),
好处
- 少了一次指针定位的开销,对象的访问在java中十分频繁,所以十分可观。
缺点
- 每次移动对象,reference都要同步更新。
3对象
https://x-leonidas.github.io/2025/06/19/04Java/JVM/3对象/