JUC并发编程第十章——Java对象内存布局和对象头

1 面试题

  • 说下JUC,AQS的大致流程
  • CAS自旋锁,是获取不到锁就一直自旋吗?CAS和synchronized区别在哪里,为什么CAS好,具体优势在哪里?
  • sychronized底层是如何实现的,实现同步的时候用到了CAS 了吗?具体哪里用到了?
  • 对象头存储那些信息?长度是多少位存储?

2 Object object = new Object()谈谈你对这句话的理解?

  • 位置所在-------->JVM堆->新生区->伊甸园区
  • 构成布局-------->对象头+实例数据+对齐填充

3 对象在堆内存中布局

在HotSpot虚拟机里,对象在堆内存的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data) 和对齐填充(Padding)。

3.1 对象头

在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节

由两部分组成:对象标记、类元数据

提出些问题,引出后面概念

public class Demo01 {
    public static void main(String[] args) {
        Object o = new Object();//new 一个对象,内存占多少,记录在哪里?

        System.out.println(o.hashCode());//每个对象都有hashCode,这个hashCode又是保存在哪里?

        synchronized (o){//加锁信息又是记录在哪里的?

        }
        System.gc();//手动垃圾收集中,15次可以从新生代到老年代,那这个次数又是记录在哪里的?
    }
}

问题中这些信息都是保存在对象标记 (Mark Word) 中的

3.1.1 对象标记(Mark Word)

Mark Word的存储内容

在64位系统中,对象标记占了8个字节,类型指针占了8个字节,一共是16个字节。所以如果只是new一个对象,这个对象没有属性,就没有实例数据,那么这个对象就是一个只有对象头的实例对象,那所占内存就是16个字节(忽略压缩指针的影响)
在64位系统中,对象标记占了8个字节,就是64bit大小的,其存储结构如下:

它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。

这些信息都是与对象自身定义无关的数据 (Java系统所带的一些东西,不是我们认为创建的),所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。

默认存储对象的HashCode、分代年龄和锁标志等信息。


3.1.2 类元信息(类型指针)

对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象哪个类的实例

所谓的类元信息(类型指针)其实就可以说是模板

注:Customer c1 = new Customer(); 

其中:左边Customer 就是类模板(也是类元信息(类型指针)),c1就是在栈里面的引用。等号右边的整体:new Customer(); 就是在堆中的一个实例对象。

2 实例数据

存放类的属性(Field)数据信息,包括父类的属性信息

3 对齐填充(保证8个字节的倍数)

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

有个案例,对象头16+实例数据5+对齐填充3=24字节

//一个对象头的实例对象,16字节(对象头:MarkWord(8字节) + 类型指针(8字节))
//+实例数据:int类型(4字节) + boolean类型(1字节)
//+对齐填充:3字节 = 24字节(8字节的整数倍)
class Customer{
    int id;
    boolean flag = false;
}

一个对象头的实例对象,16字节(对象头:MarkWord(8字节) + 类型指针(8字节))
+实例数据:int类型(4字节) + boolean类型(1字节)
+对齐填充:3字节 = 24字节(8字节的整数倍)

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


3.2 聊聊Object obj = new Object()

JOL(Java Object LayOut)可以帮助分析对象在Java虚拟机中的大小和布局

JOL使用
直接将以下依赖直接粘贴到maven项目的pom文件中即可

<!-- 定位:分析对象在JVM的大小和分布 -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

演示一 | 用自带的类

//第一个演示,16bytes演示
public class Demo01 {
    public static void main(String[] args) {
        Object o = new Object();//----------新建一个Object对象就是  16bytes
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
//java.lang.Object 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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
//     12     4        (loss due to the next object alignment)
//Instance size: 16 bytes
//Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    }
}

这里丢下一个疑问,为什么类型指针是4字节?之前不都是说是8字节的吗?(因为压缩指针默认开启了,后面有讲)

演示二 | 用自己的类

//只有对象头,没有实例数据,依然是16byte
public class Demo01 {
    public static void main(String[] args) {
        Customer c1 = new Customer();
        System.out.println(ClassLayout.parseInstance(c1).toPrintable());
//com.zhang.java.Customer 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
//     12     4        (loss due to the next object alignment)
//Instance size: 16 bytes
//Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    }


}
class Customer{

}

//有了对象头,且有实例数据(int+boolean),它进行了对齐填充,到了24byte
public class Demo01 {
    public static void main(String[] args) {
        Customer c1 = new Customer();
        System.out.println(ClassLayout.parseInstance(c1).toPrintable());
//com.zhang.java.Customer 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
//     12     4       int Customer.id                               0
//     16     1   boolean Customer.flag                             false
//     17     7           (loss due to the next object alignment)
//Instance size: 24 bytes
//Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
    }


}
class Customer{
    int id;
    boolean flag = false;
}

GC年龄采用4位bit存储,最大位15,例如MaxTenuringThreshold参数默认值就是15

  • 对象分代年龄最大就是15
    在这里插入图片描述

如果想证明一下

  • 我们假如想直接把分代最大年龄修改为16会直接报错。

-XX:MaxTenurningThreshold=16

尾巴参数说明(压缩指针相关)

压缩指针相关的命令(压缩指针是否开启对我们new一个对象是不是16字节的影响)

查看当前JVM运行参数的指令
java -XX:+PrintCommandLineFlags -version

压缩指针默认是开启的
(这也就解释了为什么前面的类型指针是4个字节,节约了内存空间)

假如不压缩的情况?我们手动关闭压缩指针看看?
+是开启,-就是关闭,所以指令是-XX:-UseCompressedClassPointers

也要注意,不管是否开启压缩指针,创建一个对象就是16字节的。(开启压缩指针后缺失的会由对齐填充补充)

换成其他对象试试
同样的道理

  • 42
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值