计算对象大小需要了解java对象内存布局。在HotSpot 虚拟机中,对象在内存中布局分为三块区域,对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头
对象头主要包括Mark Word,对象指针,数组长度
Mark Word
Mark Word占用8字节空间,其主要用于锁升级。
无锁:Mark Word保存对象HashCode,锁标志位是01,是否偏向锁为0。
偏向锁:请求进来先检查是否包括线程id,没有的话保存线程id,修改是否偏向锁标识。如果包括线程id,则判断线程id是否是本线程id,是的话继续向下执行,不是的则进行抢锁操作,抢锁成功更改线程id,失败则升级为轻量级锁。
轻量级锁:偏向锁抢夺失败后升级为轻量级锁,jvm通过cas操作在当前线程的线程栈中开辟一块单独的空间保存指向对象锁Mark Word的指针,并且在对象锁Mark Word中保存指向这片空间的指针,如果保存成功,则表示当前线程抢到锁,继续执行。保存失败则表示抢锁失败。
轻量级锁抢锁失败jvm使用自旋锁不断重试抢锁避免线程阻塞,当达到一定次数后(jdk1.7后jvm控制自旋次数),升级为重量级锁。
-XX:-UseSpinning 控制是否开启自旋锁,默认开启
重量级锁: 自旋锁自旋一定次数还没获得锁则升级为重量级锁,此时只有获取到锁的线程能执行,其余线程阻塞。
对象指针
指向对象元数据指针,虚拟机通过指针确定对象。
开启指针压缩:-XX:+UseCompressedOops,开启后占用4字节,默认开启。
关闭指针压缩:-XX:-UseCompressedOops ,关闭后占用8字节。
数组长度
数组对象额外占用4字节。
实例数据
实例数据指对象内定义各种类型的字段。
数据类型 | 占用空间 |
boolean | 1字节 |
byte | 1字节 |
short | 2字节 |
char | 2字节 |
int | 4字节 |
float | 4字节 |
double | 8字节 |
long | 8字节 |
对齐填充
占位作用,HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍。
对象大小计算
空对象大小计算:Mark Word 8字节 +开启指针压缩4字节=12字节,由于需要对齐填充,则空对象为16字节。
对象中包含一个int类型字段:Mark Word 8字节 +开启指针压缩4字节+int 4字节=16字节,此时正好为8的倍数,不需要对齐填充。
程序计算对象大小
lucene提供计算对象大小方法
<!-- 引入lucene -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.0.0</version>
</dependency>
public static void main(String[] args) {
//新建测试对象
TestObjectSize testObjectSize = new TestObjectSize();
// 计算指定对象及其引用树上的所有对象的综合大小
System.out.println(RamUsageEstimator.sizeOf(testObjectSize));
// 计算指定对象本身在堆空间的大小
System.out.println(RamUsageEstimator.shallowSizeOf(testObjectSize));
// 计算指定对象及其引用树上的所有对象的综合大小
System.out.println(RamUsageEstimator.humanSizeOf(testObjectSize));
}