背景简化:最近由于项目需要,需要计算一下对象的大小,防止放开灰度后导致服务期频繁GC
读完这篇文章可以获得什么?
- 对象的内存布局
- 指针压缩的原理
- 预估对象的大小
- 对象是否只能在堆上分配
基础
1、对象的内存布局
一个Java对象在内存中包括对象头、实例数据和补齐填充3个部分
由于本文主要是讲述对象的大小计算,所以不会详细讲解每个部分的作用,有兴趣可以上网搜索一些相关文章阅读。
对象头
所有对象都会有的部分:
Mark Word
Mark Word 用于存储对象自身的运行时数据
如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID 、偏向时间戳等
这部分数据的长度在32 位和64 位的虚拟机中分别为32 bit 和64 bit
Klass Pointer
用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。
在32位系统占4字节,在64位系统中占8字节;
64位机中开启指针压缩占4字节
注意是klass 而不是class
许多文章都是class pointer可参考HotSpotGlossary
数组对象存在的部分:
Length
如果是数组对象,还有一个保存数组长度的空间,占4个字节
Padding
如果是数组对象且未开启指针压缩则还会存在一个padding用来对齐
这个部分很多文章都省略了
但确实存在
Instance Data
对象真正存储的有效的信息,程序代码中定义的各种的数据的类型
如果有继承的关系,还有继承父类的字段。
分配策略(参数FiedsAllocationStyle)影响java中定义的顺序,对相同宽度的字段总是被分配到一起,在这种情况下,父类定义的变量会出现在子类之前。CompactFields 为true (默认为true) 子类中较窄的变量也可能插入到父类变量中。
HostSpot 的默认分配策略为
- longs/doubles
- ints
- shorts/chars
- bytes/booleans
- oops(Ordinary Object Pointers)
数据类型分为基本数据类型和引用数据类型
基本数据类型
Type | Bytes |
---|---|
boolean | 1 |
byte | 1 |
short | 2 |
char | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
引用数据类型
在32位系统占4字节,在64位系统中占8字节
Padding
由于 HostSpot VM 的自动内存管理系统要求对象的起始地址必须是8字节的整数倍
换句话说对象的大小必须为8字节的整数倍,要是实例数据没有对齐,则需要进行对齐填充来补全
以8字节对齐还是16字节对齐可以配置
这部分没有特殊意义填充0值
2、指针压缩
64位的JVM支持 -XX:+UseCompressedOops 来开启指针压缩功能 1.6 后默认开启
启用CompressOops后会压缩的对象:
1、每个Class的属性指针(静态成员变量)
2、每个对象的属性指针
3、普通对象数组的每个元素指针
当然,压缩也不是所有的指针都会压缩,对一些特殊类型的指针,JVM是不会优化的
例如指向PermGen的Class对象指针、本地变量、堆栈元素、入参、返回值和NULL指针不会被压缩。
指针压缩的实现原理
前提条件
java对象默认按8字节对齐
假设内存中只有三个对象 t1 = 16字节 t2 = 32字节 t3 = 24字节
再假设分配内存是从0开始分配 则三个对象的内存地址为
- 第一个内存地址 0X00000
- 第二个内存地址 0X10000
- 第三个内存地址 0X30000
这时候再想一下以8字节分配有什么特点?后三位永远都是0
结果
实现原理为存储
的时候后三位0
抹除 0X00 0X10 0X30``````使用
的时候后三位补0
实际就是一个编码和解码的过程,针对指针压缩也有一些优化,如零基压缩,由于本文是希望尽可能简单的将压缩的实现原理,所以不再这篇文章赘述,感兴趣的可以搜一些相关文章查看
问题
一个oop所能表示的最大的内存空间是多少? 2的35次方 = 32G
为什么是 35呢? 开启指针压缩后 一个oop的大小是4字节 = 32 位 再加上取出后 后三位会补充0 所以是 32+3 =35
怎么样扩大oop的最大的内存空间呢?
改为16 /32 或者更大的 字节对齐 但是这样做的会导致空间的浪费没有必要
对象大小的计算
有了上边的基础,我们再来进行对象大小的计算
1、没有实例数据的对象
public class EmptyTuan {
public static void main(String[] args) {
//使用jol计算对象的大小(单位为字节):
System.out.println(ClassLayout.parseClass(EmptyTuan.class).toPrintable());
//使用jol查看对象的内存布局:
System.out.println(ClassLayout.parseInstance(new EmptyTuan()).toPrintable());
}
}
开启指针压缩
对象内存分布
org.learn.code.jvm.classsize.EmptyTuan object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
16 = 8 (mark word) + 4 (klass Pointer) + 0 (Instance Data) + 4 (padding)
由于按8字节对齐所以浪费了4个字节
关闭指针压缩
-XX:-UseCompressedOops
对象内存分布
org.learn.code.jvm.classsize.EmptyTuan object internals:
OFFSET SIZE TYPE DESCRIPTION