本文咱们将介绍一下关于目标的内存布局&怎么拜访堆中的目标。
前言
Object object = new Object()谈谈你对这句话的了解?一般来说在JDK8依照默认的情况下,new一个目标占多少内存空间。
- 之前讨论过目标其方位存储在堆中(常规情况:伊甸园→S0/S1→老年代)
- 现在讨论起布局,即目标的构成是什么?头体?
目标内存布局
在HotSpot虚拟机里,目标在堆内存中的存储布局能够划分为三个部分:目标头(Header)、实例数据(Instance Data)和对齐填充(Padding)
1)目标头
目标头包含两部分数据:
- 运行时元数 Mark Word
- 类型指针 Class Pointer
假如目标是数组,则还需记载数组的长度。如下图所示:
运行时元数据 Mark World
-
存储HashCode、目标年龄、锁状况标志、线程持有的锁等信息
-
在64位体系中,Mark Word占了
8
个字节,类型指针占了8
个字节【敞开指针紧缩是4字节】,一共是16(12)个字节; -
假如new一个目标,没有实例数据的话,便是16个字节【默认是开始指针紧缩的-XX:+UseCompressedClassPointers,对齐填充4字节】
-
Mark Word的存储结构如下图所示
类型指针 Class Pointer
- 指向方法区的类元信息(目标模板),虚拟机经过这个指针来确定目标是哪个类的实例
2)实例数据
- 是目标真实存储的有用信息,即类中定义的各种类型属性(包含从父类承继下来的和自身定义的)。
- 实例数据寄存具有一定规则:
- 相同宽度的字段总是被分配在一起;
- 父类中定义的变量会出现在子类之前;
- 假如CompactFields参数为true(默认为true):子类的窄变量或许插入到父类变量的空隙
3)对齐填充
- 虚拟机要求目标起始地址有必要是8字节的整数倍【详细原因这儿就不赘述了,读者可自行查阅相关资料】。
- 填充数据不是有必要存在的,仅仅是为了字节对齐这部分内存按8字节补充对齐
4)JOL验证
引入JOL依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
运用JOL检查Object目标内部细节
运用JOL检查Customer目标内部细节
public class Customer {
int id;
boolean flag = false;
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new Customer()).toPrintable());
}
}
- 7→填充的字节数
- 声明一个Customer的实例,只有一个目标头的实例目标,12字节(敞开紧缩指针)+ 4字节[int] + 1字节[boolean]=17字节,此刻需求对齐填充到24字节
默认敞开指针紧缩的 -XX:+UseCompressedClassPointers
- 12 + 4(对齐填充) == 一个目标16字节(不算实例数据)
手动关闭指针紧缩 -XX:-UseCompressedClassPointers
- 8 + 8 == 16字节(不算实例数据)
图示目标的内存布局
目标的拜访定位
创立目标是为了后续运用该目标,那么JVM是怎么经过栈帧中的目标引用拜访到其内部的目标实例的呢?
- 经过栈上的reference拜访
而reference类型仅仅一个指向目标的引用,并没有定义这个引用应该经过什么方法去定位、拜访到堆中目标的详细方位,而JVM中主流的目标拜访方法主要有运用句柄和直接指针两种方法。
1)句柄拜访
Java堆中或许会划分出一块内存来作为句柄池,而reference中存储的便是目标的句柄地址,而句柄中包含了目标实例数据与类型数据各自详细的地址信息。
- 优点:reference中存储稳定句柄地址,目标被移动(废物收集时分移动目标很遍及)时会改变句柄中的实例数据指针即可,reference自身不需求被修改
- 缺陷:需求多占用一些空间
2)直接指针
- HotSpot运用该方法。
运用直接指针拜访的话,Java堆中目标的内存布局就有必要考虑怎么放置拜访类型数据的相关信息,reference中存储的直接便是目标地址,假如仅仅拜访目标自身的话,就不需求多一次直接拜访的开支。