JUC 二. 对象布局

一. 基础

  1. 先了解几个面试题:
  1. JUC 层面,说以下AQS大致流程
  2. CAS自旋锁,获取不到锁时会一直自旋吗? CAS 与 synchronized区别在哪,为什么CAS好,
  3. CAS 自旋时能保证当前自旋的线程一直占用cpu吗,如果cpu放弃了当前这个线程,是不是还要带来线程再次抢占cpu的开销
  4. synchronized 底层如何实现,在同步时有没有用到cas,具体在哪里用到了
  5. 对象头中存储了那些信息,长度是多少
  6. Object obj = new Object(): 说一下new一个对象,在jdk8下默认会占用多少内存空间
  1. JVM中对象布局: 在HotSpot虚拟机(也就是我们常说的java虚拟机)中,对象在堆内存中,存储布局可划分为三个部分:对象头Header, 实例数据Instance Data, 和对齐填充Padding
  2. 注意点在JVM虚拟机中可以将对象分为两种:JavaObject与ArrayObject,两种类型的对象唯一区别是ArrayObject对象头中多了一个Length
    在这里插入图片描述

对象头

  1. 对象头由: 对象标记MarkWord, 类型指针Class Pointer(又叫类元信息klass pointer) 两部分组成
  2. 其中类型指针(类元信息)中存储的是指向该对象类元数据的首地址
  3. MarkWord对象标记中存储了对象在runtime时用的信息: 哈希码(HashCode),GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳,转发指针等等
  1. hashCode: 可以理解为对象的身份id,例如我们去获取一个对象的hashCode时,实际获取的就是该值
  2. GC 标记:
  3. GC 分代年龄(分代次数): 例如垃圾回收时,什么时候在新生代,什么时候放入老年代,等等回收判断次数就是根据次数进行记录,获取判断的
    在这里插入图片描述在这里插入图片描述
  1. 在 64位系统中 MarkWord 与 类型指针 各占8个字节,一共16个字节,在64位系统中对象头MarkWord 占64bit位,存储结构大致如下
  1. 其中 “25bit unused” 前25位不使用
  2. “31bit” 用来存储hashCode编码
  3. 用 “4bit” 存储分代年龄, 例如存储最大分代年龄为"1111" 二进制转10进制,我们了解到最大分代年龄为15次
  4. 一位"1bit"用来标记是否是偏向锁
  5. “2bit”: 2位存储锁标志位
    在这里插入图片描述
    在这里插入图片描述
  1. 类型指针: 对象指向它的类元数据的指针,虚拟机通过这个指针来确定当前对象时哪个类的实例,对象实例在堆空间中,类元信息在方法区中
    在这里插入图片描述
  2. 总结: 对象头由: 对象标记MarkWord 与 类型指针Class Pointer(又叫类元信息) 两部分组成, MarkWord对象标记中存储了对象: 哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳、转发指针等等信息,在64位系统中对象头MarkWord 占64bit位,其中31位用来存储hashCode, 4位用来存储分代年龄,通过此处我们也可以在这里看出最大年龄"1111"转10进制为15次,锁标志等信息,类型指针: 对象指向它的类元数据的指针,虚拟机通过这个指针来确定当前对象时哪个类的实例,对象实例在堆空间中,类元信息在方法区中,在64位系统中MarkWord 与类型指针分别占用8个字节大小(类型指针不压缩的情况下是8个字节,默认会开启压缩4个字节)
  3. 了解对象头源码可以查看openJDK8下的markOop.hpp,会发现内部有几个属性
    在这里插入图片描述

实例数据

  1. 存放类的属性数据信息,包括父类的属性信息等,如果是ArrayObject数组类型对象,实例数据中还包括数组长度,这部分会占用4个字节对齐
  2. 以下方自定义类MyObject 为例,内部有int类型与char类型两个属性,其中int类型为32位(32/8=4)占4个字节,char占1个字节,加上前面对象头16个字节,此时计算下来就是21个字节,在通过对齐填充扩充为8个字节的倍数+3个字节,那么该对象最少占用24个字节
class MyObject {
    int i = 1;
    char c = 'a';
}

对齐填充

  1. 对齐填充,为的是保证对象大小是8个字节的倍数,假设当前创建对象对象头+实例数据大约占14个字节,为了方便jvm计算,将其扩充为8个字节的倍数,对齐填充就设置为两个字节,这样(14+2=16) 保证了是8个字节的倍数,假设创建的对象刚好是8个字节的倍数,那么对齐填充将不会补充

jol-core工具证明对象布局与压缩指针

  1. 项目中引入一个依赖
		<dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
  1. 示例代码(通过运行main方法,创建Object对象,通过jol-core下的ClassLayout查看Object对象细节)
import org.openjdk.jol.info.ClassLayout;

public class ObjectHeader {
    public static void main(String[] args) {
        //1.获取JVM细节
        //System.out.println(VM.current().details());
        //2.
        //System.out.println(VM.current().objectHeaderSize());
        //3.
        //System.out.println(VM.current().objectAlignment());

        //4.创建一个Object对象
        Object obj = new Object();

        //5.查看Object对象细节
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}
  1. 打印输出如下图: 其中:
    在这里插入图片描述
  1. 注意点,如果要看后面VALUE中的数字编码需要从后向前看
  2. 在第二个红框处,说明当前创建的对象占12个字节,补齐填充加了4个字节
  3. 在第三个红框处说明总共占16个字节
    在这里插入图片描述
  1. 由上面打印出来的数据我们看到: Object实际占有了12个字节,由于不是8字节的倍数,通过对齐填充补了4个最终对象占16个字节,那么我们前面学对象头的时候,说了在 64位系统中对象头中 MarkWord 与 类型指针各占8个字节,为什么我们打印出来的类型指针只有4个字节
  2. 在了解一个命令(查看JVM启动时使用参数): “java -XX:+PrintCommandLineFlags -version” ,发现输出JVM默认使用 " -XX:+UseCompressedClassPointers" 表示压缩CalssPointers, 所以上面我们输查看对象头中类型指针只占4个字节
    在这里插入图片描述
  3. 当我们执行" -XX:-UseCompressedClassPointers" 关闭压缩后再查看对象字节数,就发现此时类型指针变为了8个字节
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值