bilibili尚硅谷周阳老师JUC并发编程与源码分析课程笔记第十章——Java对象内存布局和对象头

本文详细讲解了Java对象在内存中的布局,包括对象头的组成、64位系统中的内存占用、实例数据和对齐填充。此外,还探讨了对象标记在不同版本虚拟机中的作用,以及如何使用JOL工具获取对象内部信息。最后提到GC分代年龄设置和压缩指针对内存影响的示例。
摘要由CSDN通过智能技术生成

Java对象内存布局和对象头

先从阿里及其它大厂面试题说起

  1. 你觉得目前面试,你还有哪些方面理解得比较好,我没问到的?答:juc和jvm以及同步锁机制
  2. 那就先说下juc,说下aqs的大致流程
  3. cas自旋锁,是获取不到锁就一直自旋吗?cas和synchronized区别在哪里,为什么cas好,具体优势在哪里?答:cas避免cpu切换线程的开销
  4. 在自旋的这个线程能保证一直占用cpu吗?假如cpu放弃了这个线程,不是还要带来线程再次抢占cpu的开销?
  5. sychronized底层是如何实现的,实现同步的时候用到了cas了吗?具体哪里用到了?
  6. 对象头存储那些信息?长度是多少位存储?

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

以前会问对象的位置所在?答:JVM堆->新生区->伊甸园区

现在会问对象的构成布局?提示:头体?想想我们的html报文!

对象在堆内存中布局

权威定义——周志明老师JVM第3版

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

在这里插入图片描述

对象在堆内存中的存储布局

  1. 对象头(在64位系统中,对象标记占了8个字节,类型指针占了8个字节,一共是16个字节)
    • 对象标记(Mark Word)
      • 默认存储对象的HashCode、分代年龄和锁标志等信息
      • 这些信息都是与对象自身定义无关的数据,所以对象标记被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据
      • 它会根据对象的状态复用自己的存储空间,也就是说在运行期间对象标记里存储的数据会随着锁标志位的变化而变化
      • 具体存储脑图如下:
  • 在这里插入图片描述

  • 在这里插入图片描述

    • 在64位系统中,对象标记占了8个字节,类型指针占了8个字节,一共是16个字节。所以如果只是new一个对象,这个对象没有属性,就没有实例数据,那么这个对象就是一个只有对象头的实例对象,那所占内存就是16个字节(忽略压缩指针的影响)
    • 在64位系统中,对象标记占了8个字节,就是64bit大小的,其存储结构如下:
    • 在这里插入图片描述
    • 类元信息(也叫类型指针,Class Pointer)
      • 存储对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象哪个类的实例
      • 具体存储脑图如下:
    • 在这里插入图片描述
  1. 实例数据
    • 存放类的属性(Field)数据信息,包括父类的属性信息
  1. 对齐填充(保证8个字节的倍数)
    • 虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按8字节补充对齐

    • 例如:

      package com.bilibili.juc.objecthead;
      
      public class ObjectHeadDemo {
      
          public static void main(String[] args) {
              Customer customer = new Customer();
          }
          
      }
      
      class Customer {
          int id;
          boolean flag = false;
      }
      

      上述代码中的customer对象就是16字节(对象头,忽略压缩指针的影响)+ 4字节(int类型)+ 1字节(boolean类型)= 21字节——>对齐填充,补到24字节

官网理论

Hotspot术语表官网

https://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

在这里插入图片描述

底层源码理论证明

https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/89fb452b3688/src/share/vm/oops/oop.hpp

在这里插入图片描述

_mark字段是对象标记,_metadata是类指针klass pointer,对象头(object header)即是由这两个字段组成

另外,在 Java HotSpot™VM 中,每个对象前面都有一个类指针和一个对象标记

在这里插入图片描述

再说对象头的对象标记

32位虚拟机对象标记(看一下即可,不用学了,以64位为准)

在这里插入图片描述

64位虚拟机对象标记(重要)

在这里插入图片描述

C语言底层源码分析markOop.hpp

在这里插入图片描述

在这里插入图片描述

hash:保存对象的哈希码

age:保存对象的分代年龄(age:4代表4位,二进制,最大为1111,转换为十进制即15,所以GC保存对象的分代年龄就是15)

biased lock:偏向锁标识位

lock:锁状态标识位

JavaThread*:保存持有偏向锁的线程ID

epoch:保存偏向时间戳

对象标记(64位)分布图

在这里插入图片描述

在这里插入图片描述

对象布局、GC回收和后面的锁升级就是对象标记里面标志位的变化

聊聊Object obj = new Object()

JOL(Java Object LayOut)证明

JOL官网

https://openjdk.org/projects/code-tools/jol/

JOL使用

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

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

import org.openjdk.jol.vm.VM;

public class JOLDemo {

    public static void main(String[] args) {
        // Java虚拟机详细信息
        System.out.println(VM.current().details());

        // 所有的对象分配的字节都是8的整数倍
        System.out.println(VM.current().objectAlignment());
    }

}

输出结果:
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

8
获取对象的内部信息
Code
package com.bilibili.juc.objecthead;

import org.openjdk.jol.info.ClassLayout;

public class JOLDemo2 {

    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        Customer1 customer1 = new Customer1();
        System.out.println(ClassLayout.parseInstance(customer1).toPrintable());

        Customer2 customer2 = new Customer2();
        System.out.println(ClassLayout.parseInstance(customer2).toPrintable());
    }

}

// 1.第一种情况,只有对象头,没有其它任何实例数据
class Customer1 {
}

// 2.第二种情况,不仅有对象头,还有实例数据,int+boolean
class Customer2 {
    int id;
    boolean flag = false;
}

输出结果:
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

com.bilibili.juc.objecthead.Customer1 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)                           db cd 00 f8 (11011011 11001101 00000000 11111000) (-134165029)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

com.bilibili.juc.objecthead.Customer2 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)                           19 ce 00 f8 (00011001 11001110 00000000 11111000) (-134164967)
     12     4       int Customer2.id                              0
     16     1   boolean Customer2.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
结果呈现说明

在这里插入图片描述

结论
  1. 如果一个对象只有对象头,没有其它任何实例数据,默认就是16个字节
  2. 如果一个对象不仅有对象头,还有实例数据,就是16字节+实例数据字节(+对齐填充)——>8的倍数

GC分代年龄

GC年龄采用4位bit存储,最大的二进制数就是1111,即十进制15,所以MaxTenuringThreshold参数默认值就是15,且最大只能是15,值应该在0000-1111之间,即0-15之间

设置GC分代年龄为16

在这里插入图片描述

运行报错

在这里插入图片描述

压缩指针

java -XX:+PrintCommandLineFlags -version:打印JVM虚拟机启动后,已启动的全部参数

默认开启压缩指针,开启后将上述类型指针压缩为4字节,以节约空间

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所以上面的类型指针为4字节,加Mark Word 8字节等于12字节,不足8的倍数,(loss due to the next object alignment:对齐填充)补齐4位,即16字节

在这里插入图片描述

手动关闭压缩指针: -XX: -UseCompressedClassPointers,之前代码运行结果就是:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值