怎么在Java中布局对象内存

发布时间:2021-04-30 15:32:37 作者:Leah
来源:亿速云 阅读:106

怎么在Java中布局对象内存?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

常用的java框架有哪些

1.SpringMVC,Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架。2.Shiro,Apache Shiro是Java的一个安全框架。3.Mybatis,MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。4.Dubbo,Dubbo是一个分布式服务框架。5.Maven,Maven是个项目管理和构建自动化工具。6.RabbitMQ,RabbitMQ是用Erlang实现的一个高并发高可靠AMQP消息队列服务器。7.Ehcache,EhCache 是一个纯Java的进程内缓存框架。

对象内存构成

Java 中通过 new 关键字创建一个类的实例对象,对象存于内存的堆中并给其分配一个内存地址,那么是否想过如下这些问题:

怎么在Java中布局对象内存

在 JVM 中,Java对象保存在堆中时,由以下三部分组成:

怎么在Java中布局对象内存

对象头

我们可以在Hotspot官方文档中找到它的描述(下图)。从中可以发现,它是Java对象和虚拟机内部对象都有的共同格式,由两个字(计算机术语)组成。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。

怎么在Java中布局对象内存

它里面提到了对象头由两个字组成,这两个字是什么呢?我们还是在上面的那个Hotspot官方文档中往上看,可以发现还有另外两个名词的定义解释,分别是 mark word 和 klass pointer。

怎么在Java中布局对象内存

从中可以发现对象头中那两个字:第一个字就是 mark word,第二个就是 klass pointer。

Mark Word

用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。

Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit。我们打开openjdk的源码包,对应路径/openjdk/hotspot/src/share/vm/oops,Mark Word对应到C++的代码markOop.hpp,可以从注释中看到它们的组成,本文所有代码是基于Jdk1.8。

怎么在Java中布局对象内存

Mark Word在不同的锁状态下存储的内容不同,在32位JVM中是这么存的

怎么在Java中布局对象内存

在64位JVM中是这么存的

怎么在Java中布局对象内存

虽然它们在不同位数的JVM中长度不一样,但是基本组成内容是一致的。

Klass Pointer

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

实例数据

如果对象有属性字段,则这里会有数据信息。如果对象无属性字段,则这里就不会有数据。根据字段类型的不同占不同的字节,例如boolean类型占1个字节,int类型占4个字节等等;

对齐数据

对象可以有对齐数据也可以没有。默认情况下,Java虚拟机堆中对象的起始地址需要对齐至8的倍数。如果一个对象用不到8N个字节则需要对其填充,以此来补齐对象头和实例数据占用内存之后剩余的空间大小。如果对象头和实例数据已经占满了JVM所分配的内存空间,那么就不用再进行对齐填充了。

所有的对象分配的字节总SIZE需要是8的倍数,如果前面的对象头和实例数据占用的总SIZE不满足要求,则通过对齐数据来填满。

为什么要对齐数据?字段内存对齐的其中一个原因,是让字段只出现在同一CPU的缓存行中。如果字段不是对齐的,那么就有可能出现跨缓存行的字段。也就是说,该字段的读取可能需要替换两个缓存行,而该字段的存储也会同时污染两个缓存行。这两种情况对程序的执行效率而言都是不利的。其实对其填充的最终目的是为了计算机高效寻址。

至此,我们已经了解了对象在堆内存中的整体结构布局,如下图所示

怎么在Java中布局对象内存

Talk is cheap, show me code

概念的东西是抽象的,你说它是这样组成的,就真的是吗?学习是需要持怀疑的态度的,任何理论和概念只有自己证实和实践之后才能接受它。还好 openjdk 给我们提供了一个工具包,可以用来获取对象的信息和虚拟机的信息,我们只需引入 jol-core 依赖,如下

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

jol-core 常用的三个方法

普通对象

为了简单化,我们不用复杂的对象,自己创建一个类 D,先看无属性字段的时候

public class D {
}

通过 jol-core 的 api,我们将对象的内部信息打印出来

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

最后的打印结果为

怎么在Java中布局对象内存

可以看到有 OFFSET、SIZE、TYPE DESCRIPTION、VALUE 这几个名词头,它们的含义分别是

怎么在Java中布局对象内存

可以看到,d对象实例共占据16byte,对象头(object header)占据12byte(96bit),其中 mark word占8byte(64bit),klass pointe 占4byte,另外剩余4byte是填充对齐的。

这里由于默认开启了指针压缩,所以对象头占了12byte,具体的指针压缩的概念这里就不再阐述了,感兴趣的读者可以自己查阅下官方文档。jdk8版本是默认开启指针压缩的,可以通过配置vm参数开启关闭指针压缩,-XX:-UseCompressedOops

怎么在Java中布局对象内存

如果关闭指针压缩重新打印对象的内存布局,可以发现总SIZE变大了,从下图中可以看到,对象头所占用的内存大小变为16byte(128bit),其中 mark word占8byte,klass pointe 占8byte,无对齐填充。

怎么在Java中布局对象内存

开启指针压缩可以减少对象的内存使用。从两次打印的D对象布局信息来看,关闭指针压缩时,对象头的SIZE增加了4byte,这里由于D对象是无属性的,读者可以试试增加几个属性字段来看下,这样会明显的发现SIZE增长。因此开启指针压缩,理论上来讲,大约能节省百分之五十的内存。jdk8及以后版本已经默认开启指针压缩,无需配置。

数组对象

上面使用的是普通对象,我们来看下数组对象的内存布局,比较下有什么异同

public static void main(String[] args) {
    int[] a = {1};
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
}

打印的内存布局信息,如下

怎么在Java中布局对象内存

关于怎么在Java中布局对象内存问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。

推荐阅读:
  1. C++对象继承中内存布局的示例分析
  2. Java对象的内存布局及访问定位是什么

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:怎么在R语言数据框中使用负索引

下一篇:使用JavaScript怎么控制电灯开关

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》