本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Java对象内存布局
位置 | 存储内容 | 所占大小 |
---|---|---|
对象头 | 8字节的标志位,存储分代年龄,hashcode(获取才会存储)等标志 8字节的classpointer,指向类的元数据地址,方法区 如果是数组对象,4字节存储数组的大小长度,如果不是数组对象,则不包含 | 8字节(64位系统) +8字节 +4字节 |
实例数据 | 存储着对象包含的所有成员变量,大小根据不同的数据类型空间不同 boolean和byte是1字节 short和char是2字节 int和float是4字节 long和double是8字节 引用类型是8字节 | |
填充数据 | 将对象的所占空间填充为8字节的整数倍,以适配CPU的读取,是空间换时间的思想 |
目的
了解Java的对象布局,可以在相关高并发场景下,计算出会产生多大的内存,用来调整JVM大小
工具JOL Demo
maven依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>RELEASE</version>
</dependency>
复制代码
demo代码
package com.bayitalk.clazz;
import org.openjdk.jol.info.ClassLayout;
/**
* 测试对象的布局
*/
public class TClass {
private final static int a = 20;
private static int b = 129;
private int c = 12;
private int d = 10;
private static String comment = "This is Test class";
public String getComment(){
return comment;
}
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new TClass()).toPrintable());
}
}
复制代码
输出结果
com.bayitalk.clazz.TClass object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800c105
12 4 int TClass.c 12
16 4 int TClass.d 10
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
复制代码
解析
- 8个字节的标志位,并且是大端存储
- 标志位中并没有hashcode的值,是因为hashcode只有调用了hashcode相关的方法才会存储进标识位
- class pointer是4个字节,因为存在指针压缩,为了减少空间和减少GC的频次
- 实例对象只存储了成员变量
- 有4个字节的填充字段,用来填充成8字节的整数倍
备注
小端存储:便于数据类型转换,例如long转int可以直接舍弃
大端存储:便于符号判断,最低位是符号位
指针压缩超过32G会失效。32位的处理器,内存大概4G
ClassPointer
classpointer存储的是指向类元数据(方法区)的地址,因此Java采用的是直接指针
访问对象.
句柄池访问对象
直接指针指向
句柄池和直接引用的区别
访问方式 | 定义 | 优劣势 |
---|---|---|
句柄池访问对象 | 句柄池访问,会再堆中单独开辟一个空间,存储对象实例数据和类元类型数据的指针 | 优势:reference存储的是句柄的地址,在对象实例移动的时候,不需要维护reference,只需要修改句柄池就好。 劣势:需要额外的存储空间,增加了一次指针跳转的开销 |
直接指针指向 | 在对象的实例数据中直接存储类元类型数据的指针 | 优势:减少了一次指针定位的开销 劣势:在GC等移动复制对象的时候,需要维护相应的reference |
指针压缩
- 为了保证CPU缓存,存储更多的对象指针,将8字节压缩成4字节
- 减少GC发生的频次,占更少的资源
- 关闭指针压缩 : -XX:-UseCompressedOops
- 64位系统,4G内存默认开启指针压缩,超过32G之后,指针压缩失效,32位系统的CPU 最大支持2^32 = 4G ,如果是64位系统,最大支持 2^64, 但是对其填充是按照8字节进行填充,指针压缩可以理解为在32位系统在64位上面使用,因为32位系统的CPU寻址空间最大支持4G,对其进行8字节填充 = 32G,这就是内存超过32G指针压缩失效的原因。
对齐填充
-
对齐填充,是为了提高CPU的访问效率
-
因此对对象的实例数据进行更好的排序,有助于对齐填充产生的内存碎片
-
对象的实例数据顺序,并不一定是编码顺序,这个和Hotspot的
FieldsAllocationStyle
取值有关系。
FieldsAllocationStyle取值
取值 | 含义 | 备注 |
---|---|---|
0 | 基本数据类型的原始引用 > 填充字段 > 引用类型 | 填充的字节是为了对齐8字节填充的空白 |
1 | 引用类型 > 基本类型 > 填充字段 | 默认1 |
2 | 父类的引用和子类的引用放在一块,父类策略采用0,子类策略采用1,但是父子类的数据是有隔离的 | 如果一个项目的继承关系特别多,采用策略2,可以提高GC的销量,父子类的引用在一起,方便查找 |