首页 > 科技 > Java对象的内存布局相关知识点

Java对象的内存布局相关知识点

概述

    我们知道关于Java对象的内存布局是由JVM所管理,所以我们就从Java虚拟机的官方文档出发,了解Java对象的内存布局。

对象内存布局

     对象在内存中存储的布局可以分为3块区域:对象头实例数据对齐填充

对象头

这是OpenJDK里hotspot的文档关于对象头的描述:

大致意思是: 是每个gc管理的堆对象开头的公共结构。(每个oop指针都指向一个对象头。)包括堆对象的布局、类信息、GC状态、同步状态和标识哈希码的基本信息。由两个字组成。在数组中,它后面紧跟着一个长度字段。注意,Java对象和vm内部对象都有一个通用的对象头格式。

这里解释以下oop:

关于oop的描述:

大致意思: 是一个对象指针。特别是指向gc管理的堆的指针。(这个词很传统。一个“o”可以代表“普通”。实现为本机地址,而不是句柄。Oops可能被编译或解释的Java代码直接操作,因为GC知道Oops在这些代码中的活性和位置。Oops也可以由C/ c++代码的短周期直接操作,但是必须由这些代码在每个安全点的句柄中保存。

    上面对象头中说一个对象头由两个字组成。我们来看看这两个字:

第一个字mark word

大致意思: 每个对象标头的第一个字。通常是一组位域,包括同步状态和标识哈希码。也可以是同步相关信息的指针(具有特征的低比特编码)。在GC期间,可能包含GC状态位。

意思是用于存储对象自身的运行时数据:(如哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等)。称“Mark Word”,(考虑到存储成本,是一个非固定的数据结构)。

我们来看看mark word里的具体信息:

下面这段代码是Open JDK里关于mark word的源码

这里面的注释,说明了32位机和64位机的mark word信息。

我们来看64位下对mark word的描述。

unused 25 :未使用25位

hash:31 :哈希占了31位

age:4 :表示老年代,新生代的占了4位。(这里需要说明一下,对象在Survivor区每“熬过”一次Minor GC ,对象的年龄就会+1,默认值为15,超过这个值就会晋升到老年代中。因为4位可以表示的最大值为15)。

biased_lock:1: 表示偏向锁占1位

lock:2 : 表示锁占2位

对象有5种状态:无锁、轻量级锁、重量级锁、偏向锁、GC。

HotSpot虚拟机对象头Mark Word

第二个字 klass pointer

大致意思: 每个对象标头的第二个字。指向描述原始对象的布局和行为的另一个对象(元对象)。对于Java对象,“klass”包含一个c++风格的“vtable”。

意思是:另一部分是类型指针。(即对象指向它的类元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的实例)。

注意:对象头一共占128位,其中mark word占64位,klass pointer占64位

实例数据

    是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。这部分的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响。

对齐填充

下面结合对象信息来介绍。

这里我们可以通过编写一个类,把该类的对象信息输出验证以下:

那么问题来了,Java如何输出一个对象的信息呢?

OpenJDK里有一个jol(java object layout)的jar包。可以输出对象的信息。

一个普通类:

public class Simple {

private int state = 1; //只有一个成员

}

//通过main方法

private static Simple simple = new Simple();

public static void main(String[] args) {

//这句话便可以将对象信息输出

System.out.println(ClassLayout.parseInstance(simple).toPrintable());

}

来我们看输出结果:

com.aiun.test.Simple 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) 47 c1 00 f8 (01000111 11000001 00000000 11111000) (-134168249)
12 4 int Simple.state 1
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

我们可以清楚的看到,对象头输出了三块,每一块4字节,一共12字节。

但是这里我们只看到了对象头和实例数据,并没有看到对齐填充,怎么回事?

来,我们在给类加一个属性。

public class Simple {

private int state = 1;

private boolean flag = true;

}

//来,我们看输出结果

com.aiun.test.Simple 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) 47 c1 00 f8 (01000111 11000001 00000000 11111000) (-134168249)

12 4 int Simple.state 1

16 1 boolean Simple.flag true

17 7 (loss due to the next object alignment)

Instance size: 24 bytes

Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

这次我们可以看出来,输出结果和上一次不一样了,多了 (loss due to the next object alignment) 意思是:由于下一次对象对齐而造成的损失

所以这另外的7个字节并不属于类信息,而是对齐填充造成的。

注意 对齐填充: 仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍。

那么为什么是8字节的整数倍,这样有什么优点呢?

系统要求

可以提高GC回收效率

接下来讨论一下mark word里面的信息:

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)

就这个输出信息来讨论:

*上面说对象头一个占128位,为什么这里打印出来占12字节96位呢?

这是因为Java虚拟机默认开启了指针压缩。由于在64位CPU下, 指针的宽度是64位的, 而实际的heap区域远远用不到这么大的内存, 使用64bit来存对象引用会造成浪费, 所以应该压缩来节省资源。可以通过-XX:-UseCompressedOops参数来关闭指针压缩。

根据上面源码注释里面说,前25位是未使用,应该全为0,为什么对象信息输出后,第8位就为1?有1不是说明有数据吗?

因为存储是分大端、小段存储的。小端存储是反着来存储的(也就是高地址低字节),大端存储是顺着来存(高地址高字节)。所以这里是反正存储的。

就算反着来看,那接下来31位为hash,怎么全是0呢?

按理说Java的hashCode()确实存在,为什么这里没有呢?我们输出一下hashCode看看

21685669

0 4 (object header) 01 a5 e5 4a (00000001 10100101 11100101 01001010) (1256563969)

4 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)

我们看到调用了hashCode()方法后,对象输出信息里就有hash值了,因为hash值是通过C++代码计算的。
我们将输出的结果转换为16进制:21685669 14AE5A5,可以看出输出的确实是hash码。

最后面的3个字节表示锁的状态,我们现在来加一下锁,看看

public static void main(String[] args) {

Simple simple = new Simple();

synchronized (simple) {

System.out.println(ClassLayout.parseInstance(simple).toPrintable());

}

}

输出

0 4 (object header) 70 f6 ad 02 (01110000 11110110 10101101 00000010) (44955248)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

最后三位为110,1表示偏向锁状态,10表示重量级锁(synchronized)。

总结:

我们可以看到出一个对象信息涉及到很多知识,对象状态、指针压缩、GC年龄、偏向延迟、批量撤销、锁膨胀可逆等相关知识。

————————————————

版权声明:本文为CSDN博主「CodAlun」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/CodAlun/article/details/102987364

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.sosokankan.com/article/2217588.html

setTimeout(function () { fetch('http://www.sosokankan.com/stat/article.html?articleId=' + MIP.getData('articleId')) .then(function () { }) }, 3 * 1000)