区块链技术博客
www.b2bchain.cn

06-对象的创建过程与内存布局求职学习资料

本文介绍了06-对象的创建过程与内存布局求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

  • 一 对象的内存布局
    • 1.对象头(Object Header)
    • 2.实例数据(Instance Data)
    • 3.对齐填充(Padding)
    • 4.内存布局示意图
  • 二 完成对象的创建
    • 1. 设置对象头
    • 2. 执行init方法
  • 三 指针压缩
    • 1. 计算机寻址
    • 2. 指针压缩

    在上一节我们了解了对象的创建过程中的内存分配以及逃逸分析,当对象已经获得了开辟的内存空间后,则要对这块空间进行初始化(赋予零值),由于初始化的过程很简单,这里就简单的描述一下,初始化的操作保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,经过初始化后的对象,程序是能够访问到它的属性字段的,不过这个时候它们的值都为0值。如果在分配内存时,开启了TLAB,那么初始化的工作也可以提前到TLAB分配空间时进行。
    当初始化完成后,则需要对对象头进行设置。在这之前,先看看一个对象在堆内存中的存储布局到底是什么样的。

一 对象的内存布局

    在HotSpot虚拟机里,对象在堆内存中的布局可以分为三个部分:对象头、实例数据、对齐填充。
06-对象的创建过程与内存布局

1.对象头(Object Header)

    一般对象头包括两部分:第一部分用于存储对象自身的运行时数据(称之为Mark Word);第二部分为类型指针; 如果是数组对象,除了前面的两个部分外,还有第三部分用于记录数组的长度。
06-对象的创建过程与内存布局

1.MarkWord

    存储运行时数据,比如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。这部分数据的长度再32位和64位的虚拟机中分别为 32 个比特和 64个比特(未开启压缩指针)。MarkWord是一个被设计成有动态定义的数据结构,因此可以在极小的空间内存储更多的数据,根据对象的状态来复用自己的存储空间,它根据锁标志位存储了五种状态下的数据信息。如下图所示,例如在32位的虚拟机中,在无锁态下的对象,其对象头会用25个比特位来存储对象哈希码,4个比特存储对象分代年龄,一个比特位0表示非偏向锁,最后的2个比特存储锁标志位。(下图Epoch代表时间戳)
06-对象的创建过程与内存布局

2.类型指针

    即指向该对象的类型元数据的指针,虚拟机通过这个指针来确定该对象是哪个类的实例。开启指针压缩时长度为4字节,关闭指针压缩时长度为8字节。

3.数组长度

    这一部分内容只有在数组对象中才会存在,虚拟机通过这个来确定数组的大小。长度为4字节。

2.实例数据(Instance Data)

    该部分是存储的真正的数据信息,即我们在程序代码里面所定义的各种类型的属性,包括从父类继承下来的字段信息都被记录在这部分中。这些字段的存储顺序会受到虚拟机配置和在Java源码中定义顺序的影响。虚拟机默认的存储顺序为 longs/doubles、ints、shorts/chars、bytes/booleans、oops(OrdinaryObjectPointers,OOPs),相同宽度的字段总是被分配到一起存放,父类中定义的变量放在子类的变量之前。如果虚拟机配置了+XX:CompactFields参数值为true(默认为true),子类中位数较短的变量也允许放在父类变量的空隙中,从而可以节省空间。

3.对齐填充(Padding)

    该部分没有特别的含义,并不是必然存在的,它仅仅起着占位符的作用。虚拟机要求对象的起始地址必须是8字节的整数倍(即对象的大小都必须是8字节的整数倍),因此有的对象对象头+实例数据的长度不为 8 字节整数倍的时候,就需要通过对其填充来补全8字节。

4.内存布局示意图

06-对象的创建过程与内存布局

二 完成对象的创建

    回到我们对象的创建过程中,我们想要得到一个完整的对象,还需要设置对象头和执行init方法两个阶段。

1. 设置对象头

    设置对象头这一过程就是为对象内存布局中的对象头进行赋值操作,即给 Mark Word、类型指针进行赋值,如果为数组,则还要为数组长度赋值。但是要注意,对象的哈希码的设置,其实是会延后到调用 hashCode()方法才会进行计算。
    到了这个时候,对象的基本信息已经得到了确认,我们可以根据它的对象头知道其锁的状态,是哪个类的实例以及该类的元数据信息。

2. 执行init方法

    当对象头设置完成后,从虚拟机的视角来看,一个新的对象已经产生了。但是对于程序来说,这个对象才像一个刚出生的婴儿,不会说话,不会识字,不会走路跑步,对于程序而言是没有用的。因此,就需要执行init方法,为这个婴儿设置各种属性,让其变得能够为程序所用。
    看下面的代码,当我们设置好对象头的时候,其实里面的属性 a、number、age都还是0值的,只有执行init方法后,a才会变成10,number才会变成构造函数中设置的20.

public class Math {      private int a = 10;     private int number;     private int age;      public Math() {         number = 20;     } }

    将该 Math 代码编译后,使用 javap -v Math.class 命令进行查看,其中有一部分内容如下所示,首先,它会调用父类的 init 方法(这里就是调用的Object的init方法),然后为属性 a 和 number 进行赋值操作。看到这里,我们应该知道即使是 a = 10 这样的属性,虚拟机依然会把它的赋值操作放入到其init方法中进行调用。
06-对象的创建过程与内存布局
    只有执行完了这一段代码,即init方法后,一个真正可以使用的对象才算完全被构造出来。

三 指针压缩

    在前面,我们提到了指针压缩,从jdk1.6update14开始,在64bit操作系统中,JVM就支持指针压缩,那什么是指针压缩呢?先别急,在将指针压缩之前,还要对计算机如何寻址做一个了解。

1. 计算机寻址

    内存中可寻址的最小单位称为编址单位,主流计算机基本上是按字节进行编址的,即:最小可寻址单位是一个字节。理解就是,在计算机中,一个字节(8bit)组成一个地址,计算机cpu每次只能访问整个字节,而不能单独访问这个字节下的一个bit。
    对于32位的系统来说,其可用内存大小的算法应该是 寻址单位 * 2^32,如果其寻址单位是一个字节(8bit),那么其内存大小则为 2^35 = 4G。 这就是为什么对于 32位 的机器来说,其内存使用不会超过4G了。
    这里要理解的是计算机寻址的一个过程,比如我要查找内存中的某个地址,那么这个地址一定有一个编码被记录在内存中,当我在内存中读取到这个编码的时候,就知道要去查找哪个位置。
06-对象的创建过程与内存布局
    在Java中也一样,虚拟机也需要先读取到对象a的地址,然后在内存中进行寻址,因此,如果用4字节长度来保存地址信息,那么虚拟机能够寻址的范围为:2^35 也即 4G。

2. 指针压缩

    我们这里先看一段代码,首先引入依赖工具

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

    代码如下所示:

“`java
public static void main(String[] args) {
System.out.println(“==========Object 对象===============”);

  • 一 对象的内存布局
    • 1.对象头(Object Header)
    • 2.实例数据(Instance Data)
    • 3.对齐填充(Padding)
    • 4.内存布局示意图
  • 二 完成对象的创建
    • 1. 设置对象头
    • 2. 执行init方法
  • 三 指针压缩
    • 1. 计算机寻址
    • 2. 指针压缩

    在上一节我们了解了对象的创建过程中的内存分配以及逃逸分析,当对象已经获得了开辟的内存空间后,则要对这块空间进行初始化(赋予零值),由于初始化的过程很简单,这里就简单的描述一下,初始化的操作保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,经过初始化后的对象,程序是能够访问到它的属性字段的,不过这个时候它们的值都为0值。如果在分配内存时,开启了TLAB,那么初始化的工作也可以提前到TLAB分配空间时进行。
    当初始化完成后,则需要对对象头进行设置。在这之前,先看看一个对象在堆内存中的存储布局到底是什么样的。

一 对象的内存布局

    在HotSpot虚拟机里,对象在堆内存中的布局可以分为三个部分:对象头、实例数据、对齐填充。
06-对象的创建过程与内存布局

1.对象头(Object Header)

    一般对象头包括两部分:第一部分用于存储对象自身的运行时数据(称之为Mark Word);第二部分为类型指针; 如果是数组对象,除了前面的两个部分外,还有第三部分用于记录数组的长度。
06-对象的创建过程与内存布局

1.MarkWord

    存储运行时数据,比如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。这部分数据的长度再32位和64位的虚拟机中分别为 32 个比特和 64个比特(未开启压缩指针)。MarkWord是一个被设计成有动态定义的数据结构,因此可以在极小的空间内存储更多的数据,根据对象的状态来复用自己的存储空间,它根据锁标志位存储了五种状态下的数据信息。如下图所示,例如在32位的虚拟机中,在无锁态下的对象,其对象头会用25个比特位来存储对象哈希码,4个比特存储对象分代年龄,一个比特位0表示非偏向锁,最后的2个比特存储锁标志位。(下图Epoch代表时间戳)
06-对象的创建过程与内存布局

2.类型指针

    即指向该对象的类型元数据的指针,虚拟机通过这个指针来确定该对象是哪个类的实例。开启指针压缩时长度为4字节,关闭指针压缩时长度为8字节。

3.数组长度

    这一部分内容只有在数组对象中才会存在,虚拟机通过这个来确定数组的大小。长度为4字节。

2.实例数据(Instance Data)

    该部分是存储的真正的数据信息,即我们在程序代码里面所定义的各种类型的属性,包括从父类继承下来的字段信息都被记录在这部分中。这些字段的存储顺序会受到虚拟机配置和在Java源码中定义顺序的影响。虚拟机默认的存储顺序为 longs/doubles、ints、shorts/chars、bytes/booleans、oops(OrdinaryObjectPointers,OOPs),相同宽度的字段总是被分配到一起存放,父类中定义的变量放在子类的变量之前。如果虚拟机配置了+XX:CompactFields参数值为true(默认为true),子类中位数较短的变量也允许放在父类变量的空隙中,从而可以节省空间。

3.对齐填充(Padding)

    该部分没有特别的含义,并不是必然存在的,它仅仅起着占位符的作用。虚拟机要求对象的起始地址必须是8字节的整数倍(即对象的大小都必须是8字节的整数倍),因此有的对象对象头+实例数据的长度不为 8 字节整数倍的时候,就需要通过对其填充来补全8字节。

4.内存布局示意图

06-对象的创建过程与内存布局

二 完成对象的创建

    回到我们对象的创建过程中,我们想要得到一个完整的对象,还需要设置对象头和执行init方法两个阶段。

1. 设置对象头

    设置对象头这一过程就是为对象内存布局中的对象头进行赋值操作,即给 Mark Word、类型指针进行赋值,如果为数组,则还要为数组长度赋值。但是要注意,对象的哈希码的设置,其实是会延后到调用 hashCode()方法才会进行计算。
    到了这个时候,对象的基本信息已经得到了确认,我们可以根据它的对象头知道其锁的状态,是哪个类的实例以及该类的元数据信息。

2. 执行init方法

    当对象头设置完成后,从虚拟机的视角来看,一个新的对象已经产生了。但是对于程序来说,这个对象才像一个刚出生的婴儿,不会说话,不会识字,不会走路跑步,对于程序而言是没有用的。因此,就需要执行init方法,为这个婴儿设置各种属性,让其变得能够为程序所用。
    看下面的代码,当我们设置好对象头的时候,其实里面的属性 a、number、age都还是0值的,只有执行init方法后,a才会变成10,number才会变成构造函数中设置的20.

public class Math {      private int a = 10;     private int number;     private int age;      public Math() {         number = 20;     } }

    将该 Math 代码编译后,使用 javap -v Math.class 命令进行查看,其中有一部分内容如下所示,首先,它会调用父类的 init 方法(这里就是调用的Object的init方法),然后为属性 a 和 number 进行赋值操作。看到这里,我们应该知道即使是 a = 10 这样的属性,虚拟机依然会把它的赋值操作放入到其init方法中进行调用。
06-对象的创建过程与内存布局
    只有执行完了这一段代码,即init方法后,一个真正可以使用的对象才算完全被构造出来。

三 指针压缩

    在前面,我们提到了指针压缩,从jdk1.6update14开始,在64bit操作系统中,JVM就支持指针压缩,那什么是指针压缩呢?先别急,在将指针压缩之前,还要对计算机如何寻址做一个了解。

1. 计算机寻址

    内存中可寻址的最小单位称为编址单位,主流计算机基本上是按字节进行编址的,即:最小可寻址单位是一个字节。理解就是,在计算机中,一个字节(8bit)组成一个地址,计算机cpu每次只能访问整个字节,而不能单独访问这个字节下的一个bit。
    对于32位的系统来说,其可用内存大小的算法应该是 寻址单位 * 2^32,如果其寻址单位是一个字节(8bit),那么其内存大小则为 2^35 = 4G。 这就是为什么对于 32位 的机器来说,其内存使用不会超过4G了。
    这里要理解的是计算机寻址的一个过程,比如我要查找内存中的某个地址,那么这个地址一定有一个编码被记录在内存中,当我在内存中读取到这个编码的时候,就知道要去查找哪个位置。
06-对象的创建过程与内存布局
    在Java中也一样,虚拟机也需要先读取到对象a的地址,然后在内存中进行寻址,因此,如果用4字节长度来保存地址信息,那么虚拟机能够寻址的范围为:2^35 也即 4G。

2. 指针压缩

    我们这里先看一段代码,首先引入依赖工具

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

    代码如下所示:

“`java
public static void main(String[] args) {
System.out.println(“==========Object 对象===============”);

  • 一 对象的内存布局
    • 1.对象头(Object Header)
    • 2.实例数据(Instance Data)
    • 3.对齐填充(Padding)
    • 4.内存布局示意图
  • 二 完成对象的创建
    • 1. 设置对象头
    • 2. 执行init方法
  • 三 指针压缩
    • 1. 计算机寻址
    • 2. 指针压缩

    在上一节我们了解了对象的创建过程中的内存分配以及逃逸分析,当对象已经获得了开辟的内存空间后,则要对这块空间进行初始化(赋予零值),由于初始化的过程很简单,这里就简单的描述一下,初始化的操作保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,经过初始化后的对象,程序是能够访问到它的属性字段的,不过这个时候它们的值都为0值。如果在分配内存时,开启了TLAB,那么初始化的工作也可以提前到TLAB分配空间时进行。
    当初始化完成后,则需要对对象头进行设置。在这之前,先看看一个对象在堆内存中的存储布局到底是什么样的。

一 对象的内存布局

    在HotSpot虚拟机里,对象在堆内存中的布局可以分为三个部分:对象头、实例数据、对齐填充。
06-对象的创建过程与内存布局

1.对象头(Object Header)

    一般对象头包括两部分:第一部分用于存储对象自身的运行时数据(称之为Mark Word);第二部分为类型指针; 如果是数组对象,除了前面的两个部分外,还有第三部分用于记录数组的长度。
06-对象的创建过程与内存布局

1.MarkWord

    存储运行时数据,比如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。这部分数据的长度再32位和64位的虚拟机中分别为 32 个比特和 64个比特(未开启压缩指针)。MarkWord是一个被设计成有动态定义的数据结构,因此可以在极小的空间内存储更多的数据,根据对象的状态来复用自己的存储空间,它根据锁标志位存储了五种状态下的数据信息。如下图所示,例如在32位的虚拟机中,在无锁态下的对象,其对象头会用25个比特位来存储对象哈希码,4个比特存储对象分代年龄,一个比特位0表示非偏向锁,最后的2个比特存储锁标志位。(下图Epoch代表时间戳)
06-对象的创建过程与内存布局

2.类型指针

    即指向该对象的类型元数据的指针,虚拟机通过这个指针来确定该对象是哪个类的实例。开启指针压缩时长度为4字节,关闭指针压缩时长度为8字节。

3.数组长度

    这一部分内容只有在数组对象中才会存在,虚拟机通过这个来确定数组的大小。长度为4字节。

2.实例数据(Instance Data)

    该部分是存储的真正的数据信息,即我们在程序代码里面所定义的各种类型的属性,包括从父类继承下来的字段信息都被记录在这部分中。这些字段的存储顺序会受到虚拟机配置和在Java源码中定义顺序的影响。虚拟机默认的存储顺序为 longs/doubles、ints、shorts/chars、bytes/booleans、oops(OrdinaryObjectPointers,OOPs),相同宽度的字段总是被分配到一起存放,父类中定义的变量放在子类的变量之前。如果虚拟机配置了+XX:CompactFields参数值为true(默认为true),子类中位数较短的变量也允许放在父类变量的空隙中,从而可以节省空间。

3.对齐填充(Padding)

    该部分没有特别的含义,并不是必然存在的,它仅仅起着占位符的作用。虚拟机要求对象的起始地址必须是8字节的整数倍(即对象的大小都必须是8字节的整数倍),因此有的对象对象头+实例数据的长度不为 8 字节整数倍的时候,就需要通过对其填充来补全8字节。

4.内存布局示意图

06-对象的创建过程与内存布局

二 完成对象的创建

    回到我们对象的创建过程中,我们想要得到一个完整的对象,还需要设置对象头和执行init方法两个阶段。

1. 设置对象头

    设置对象头这一过程就是为对象内存布局中的对象头进行赋值操作,即给 Mark Word、类型指针进行赋值,如果为数组,则还要为数组长度赋值。但是要注意,对象的哈希码的设置,其实是会延后到调用 hashCode()方法才会进行计算。
    到了这个时候,对象的基本信息已经得到了确认,我们可以根据它的对象头知道其锁的状态,是哪个类的实例以及该类的元数据信息。

2. 执行init方法

    当对象头设置完成后,从虚拟机的视角来看,一个新的对象已经产生了。但是对于程序来说,这个对象才像一个刚出生的婴儿,不会说话,不会识字,不会走路跑步,对于程序而言是没有用的。因此,就需要执行init方法,为这个婴儿设置各种属性,让其变得能够为程序所用。
    看下面的代码,当我们设置好对象头的时候,其实里面的属性 a、number、age都还是0值的,只有执行init方法后,a才会变成10,number才会变成构造函数中设置的20.

public class Math {      private int a = 10;     private int number;     private int age;      public Math() {         number = 20;     } }

    将该 Math 代码编译后,使用 javap -v Math.class 命令进行查看,其中有一部分内容如下所示,首先,它会调用父类的 init 方法(这里就是调用的Object的init方法),然后为属性 a 和 number 进行赋值操作。看到这里,我们应该知道即使是 a = 10 这样的属性,虚拟机依然会把它的赋值操作放入到其init方法中进行调用。
06-对象的创建过程与内存布局
    只有执行完了这一段代码,即init方法后,一个真正可以使用的对象才算完全被构造出来。

三 指针压缩

    在前面,我们提到了指针压缩,从jdk1.6update14开始,在64bit操作系统中,JVM就支持指针压缩,那什么是指针压缩呢?先别急,在将指针压缩之前,还要对计算机如何寻址做一个了解。

1. 计算机寻址

    内存中可寻址的最小单位称为编址单位,主流计算机基本上是按字节进行编址的,即:最小可寻址单位是一个字节。理解就是,在计算机中,一个字节(8bit)组成一个地址,计算机cpu每次只能访问整个字节,而不能单独访问这个字节下的一个bit。
    对于32位的系统来说,其可用内存大小的算法应该是 寻址单位 * 2^32,如果其寻址单位是一个字节(8bit),那么其内存大小则为 2^35 = 4G。 这就是为什么对于 32位 的机器来说,其内存使用不会超过4G了。
    这里要理解的是计算机寻址的一个过程,比如我要查找内存中的某个地址,那么这个地址一定有一个编码被记录在内存中,当我在内存中读取到这个编码的时候,就知道要去查找哪个位置。
06-对象的创建过程与内存布局
    在Java中也一样,虚拟机也需要先读取到对象a的地址,然后在内存中进行寻址,因此,如果用4字节长度来保存地址信息,那么虚拟机能够寻址的范围为:2^35 也即 4G。

2. 指针压缩

    我们这里先看一段代码,首先引入依赖工具

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

    代码如下所示:

“`java
public static void main(String[] args) {
System.out.println(“==========Object 对象===============”);

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 06-对象的创建过程与内存布局求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们