【JUC】二十六、Java对象内存布局和对象头

2023-12-13 10:25:22

0、前置

heap(堆区),分为新生区new、养老区old、元空间Metaspace,其中new区又分为伊甸园eden、幸存者0区s0、幸存者1区s1
在这里插入图片描述
new一个对象,在内存中的位置是堆 ? 新生区? 伊甸园区

Object object = new Object();

1、对象的内存布局

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据 (Instance Data)、对齐填充 (Padding),其中,对齐填充用于保证对象内存总长度是8个字节的倍数。

在这里插入图片描述

对象头:
在这里插入图片描述

对象头分为对象标记markOop和类元信息klassOop(两种英文都行,Oop用于JDK源代码)

在这里插入图片描述

2、对象头之对象标记Mark Word

public class ObjectHeadDemo {
    public static void main(String[] args) {
        Object o = new Object();   //这样new一个对象,占内存多少?
        System.out.println(o.hashCode());  //这个hashcode记录在对象的什么地方?
        synchronized (o){ 
            //...同步锁标记?
        }
        System.gc(); //15次gc后,还没被处理的,可从新生区移动到养老区,怎么记录次数的?
    }
}

以下是对象头中的对象标记的结构:

在这里插入图片描述
在这里插入图片描述
在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。单看Mark Word,8字节,64Bit,结构如下:

在这里插入图片描述

Mark Word中默认存储对象的HashCode、分代年龄和锁标志位等信息。这些信息都与对象自身的属性定义无关,所以MarkWord被设计成一个非固定的数据结构,以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化

在这里插入图片描述
在这里插入图片描述
总之,对象布局、GC回收和锁升级就是对象标记MarkWord里面标志位的变化。

3、对象头之类元信息

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

public class Customer{

	int id;

	String name;
}


//三个对象,同一个类,即同一个模板,对象头的类元指针指向相同
Customer c1 = new Customer();
Customer c2 = new Customer();
Customer c3 = new Customer();

一句话,每次new对象出来的,指向那个统一的多个实例对象的那个模板。

在这里插入图片描述

Q:对象头多大?

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

4、实例数据

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

如下:此时new这个对象,没有属性,只有一个对象头,16字节大小(忽略压缩指针的影响)

class Customer{

}

引入属性:一个int 4 字节,一个boolean 1字节,16+4+1=21字节

class Customer{

	int id;

	boolean flag;
}

此时,21字节不是8的整数倍,需要进行对齐填充,补到24字节。

5、对齐填充

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

6、对象内存布局之JOL证明

JOL即Java Object LayOut,一个开箱即用的小工具,用于分析和展示Java对象在JVM中的大小布局。JOL官网:http://openjdk.java.net/projects/code-tools/jol/

<!--jol-->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

小试一下:

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

在这里插入图片描述

Object object = new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());

在这里插入图片描述

类型指针只有4字节,而不是前面说的8字节的事,后面说。列的含义:

在这里插入图片描述
再用上面的Customer做测试:

class Customer{

	int id;

	boolean flag;
}
Customer customer = new Customer();
System.out.println(ClassLayout.parseInstance(customer).toPrintable());

可以发现无属性时,补齐到16字节,有属性时:

在这里插入图片描述

7、对象分代年龄

在这里插入图片描述

64位虚拟机下,对象分代年龄占4bit,也就是说,最大是1111,对应十进制就是15,这也是前面说的15次以后(到最大了)会从新生区到养老区的原因。MaxTenuringThreshold参数默认值就是15,在IDEA中添加JVM参数,改为16或更大:

//不同版本的Java不太一样,可改为17
-XX:MaxTenuringThreshold=16

在这里插入图片描述

再次运行,发现创建JVM都失败了:

在这里插入图片描述

8、压缩指针

在IDEA终端执行:

java -XX:+PrintCommandLineFlags -version

查看开启的一些参数:

在这里插入图片描述

发现这里使用了压缩指针,所以说前面看到类型指针只有4字节,而非理论里的8字节

从JDK 1.6开始,64位的JVM支持UseCompressedOops选项。其可对OOP(Ordinary Object Pointer,普通对象指针)进行压缩,使其只占用4个字节,以达到节约内存的目的。在JDK 8下,该选项默认启用。

-XX:+UseCompressedOops  // 开启指针压缩
-XX:-UseCompressedOops  // 关闭指针压缩

关闭压缩:

在这里插入图片描述

继续看new Object()对象的大小和分布,此时就是8+8了:

在这里插入图片描述

文章来源:https://blog.csdn.net/llg___/article/details/134935421
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。