第十一章:Java对象内存布局和对象头

334 阅读4分钟

先从阿里及其它大厂面试题说起 在这里插入图片描述

  • Object object = new Object()谈谈你对这句话的理解?
  • 一般而言JDK8按照默认情况下,new一个对象占多少内存空间?

对象在堆内存中布局

在周志明老师JVM第3版书中 在这里插入图片描述 对象在堆内存中的存储布局,如下图所示 在这里插入图片描述 对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)。对象头分为对象标记(markOop)和类元信息(klassOop),类元信息存储的是指向该对象类元数据(klass)的首地址。

对象头 对象头由两部分组成:对象标记Mark Word和类元信息(又叫类型指针)组成。

对象标记Mark Word:默认存储对象的HashCode、分代年龄和锁标志位等信息。这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。 在这里插入图片描述 在这里插入图片描述 在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。 在这里插入图片描述 类元信息(又叫类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。简单理解就是Book b=new Book()中的Book.class模板 在这里插入图片描述 对象头多大? 在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。

实例数据 存放类的属性(Field)数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。

对齐填充 虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按8字节补充对齐。

底层源码理论证明如下: 在这里插入图片描述 _mark字段是mark word,_metadata是类指针klass pointer,对象头(object header)即是由这两个字段组成,这些术语可以参考Hotspot术语表, 在这里插入图片描述

再说对象头的MarkWord

对象标记MarkWord一共8个字节,即64位 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 上述中解释如下:

  • hash: 保存对象的哈希码
  • age: 保存对象的分代年龄
  • biased_lock: 偏向锁标识位
  • lock: 锁状态标识位
  • JavaThread* :保存持有偏向锁的线程ID
  • epoch: 保存偏向时间戳

markword(64位)分布图,对象布局、GC回收和后面的锁升级就是 对象标记MarkWord里面标志位的变化 在这里插入图片描述

聊聊Object obj = new Object()

JOL证明步骤如下: 项目引入pom依赖如下:

<!--
官网:http://openjdk.java.net/projects/code-tools/jol/
定位:分析对象在JVM的大小和分布
-->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

代码演示如下:

package com.atguigu.juc.senior.inner.object;

import org.openjdk.jol.vm.VM;

/**
 * @auther zzyy
 * @create 2020-06-13 11:24
 */
public class MyObject
{
    public static void main(String[] args){
        //VM的细节详细情况
        System.out.println(VM.current().details());
        //所有的对象分配的字节都是8的整数倍。
        System.out.println(VM.current().objectAlignment());
    }
}

在这里插入图片描述 上述代码证明,对象的大小都是8字节的整数倍。

代码2演示如下:

package com.atguigu.juc.prepare;

import org.openjdk.jol.info.ClassLayout;


/**
 * @auther zzyy
 * @create 2020-04-12 15:11
 */
public class JOLDemo
{
    public static void main(String[] args)
    {
        Object o = new Object();
        System.out.println( ClassLayout.parseInstance(o).toPrintable());
    }
}

在这里插入图片描述 在这里插入图片描述 GC年龄采用4位bit存储,最大为15,例如MaxTenuringThreshold参数默认值就是15,这是因为分代年龄占4位,最大值为1111,即15.

如果设置参数:-XX:MaxTenuringThreshold=16,即分代年龄设置成16,就会报错如下: 在这里插入图片描述 尾巴参数说明:即说明为什么上面测试的结果对象头中的类型指针为什么只有4个字节,为什么不是8个字节?

在Idea控制台执行命令:-XX:+PrintCommandLineFlags 在这里插入图片描述 因为Java虚拟机默认开启了类型指针的压缩,所以类型指针才会变成4个字节,如果不开启的话,类型指针就是8个字节。 在这里插入图片描述 在这里插入图片描述 手动关闭压缩再看看,-XX:-UseCompressedClassPointers,结果如下: 在这里插入图片描述 换成其他对象试试,结果如下: 在这里插入图片描述 在这里插入图片描述