一看就懂的Java对象内存布局(java内部对象)

createh53个月前 (01-23)技术教程32

Java对象的内存布局

1 前言

新建对象的方式:

  • Object.clone,反序列化直接复制已有数据,初始化新建对象的实例字段
  • Unsafe.allocateInstance没有初始化实例字段
  • new
  • 反射new和反射机制,都是通过调用构造器来初始化实例字段

如new编译而成的字节码包含:

  • 请求内存的new指令
  • 调用构造器的invokespecial指令

若一个类未定义任何构造器, 则Java编译器会自动添加一个无参构造器:

但子类构造器需调用父类构造器:

  • 若父类存在无参构造器,该调用可以是隐式,即Java编译器会自动添加对父类构造器的调用
  • 若父类没有无参构造器,则子类构造器需显式调用父类带参构造器

显式调用又可分为:

  • super关键字调用父类构造器
  • this关键字调用同一类中的其它构造器

都要作为构造器的第一条语句,以优先初始化继承得的父类字段。(但这也能通过调用其他生成参数的方法或字节码注入来绕开)

调用一个构造器时,优先调用父类构造器,直至Object类。这些构造器的调用者皆为同一对象,即通过new指令新建而来的对象。通过new指令新建的对象,其内存含所有父类实例字段。虽子类无法访问父类private实例字段或子类的实例字段隐藏了父类的同名实例字段,但子类实例还是会为这些父类实例字段分配内存。

那这些字段在内存如何分布的呢?

2 压缩指针

JVM,每个Java对象都有对象头(object header),由标记字段和类型指针构成:

  • 标记字段,存储JVM有关该对象的运行数据,如哈希码、GC信息及锁信息
  • 类型指针,指向该对象的类

64位JVM,对象头的标记字段占64位,类型指针又占64位。每个Java对象在内存中额外开销就是16字节。Integer类仅有一个int类型私有字段,占4字节。因此,每个Integer对象额外内存开销至少400%。这也是引入基本类型的原因之一。

为降低对象内存使用量,64位JVM引入压缩指针[1](对应虚拟机选项-XX:+UseCompressedOops,默认开启),将堆中原本64位的Java对象指针压缩成32位。

这样,对象头中的类型指针也会被压缩成32位,使对象头大小从16字节降至12字节。

2.1 作用范围

  • 对象头的类型指针
  • 引用类型的字段
  • 引用类型数组

2.2 原理

路上全是房车,而且每辆房车恰好占据两个停车位。按序编号,停在0号和1号停车位上的叫0号车,停在2号和3号停车位上的叫1号车,依次类推。

原本内存寻址用车位号。如我有一个值为6的指针,代表第6车位,沿这指针可找到3号车。

规定指针里存的值是车号,如3指代3号车。当查找3号,可将该指针值乘2,再沿着6号车位找到3号车。

这样32位压缩指针最多可标记2的32次方辆车,对应着2的33次方个车位。房车也有大小之分。大房车占据车位可能是三个甚至是更多。不过这并不会影响寻址算法:只需跳过部分车号,便可保持原本车号*2的寻址系统。

上述模型有一个前提,就是每辆车都从偶数号车位停起。这称为内存对齐(对应虚拟机选项-XX:ObjectAlignmentInBytes,默认值为8)。

默认Java虚拟机堆中对象起始地址需对齐至8倍数。如一个对象用不到8N字节,那空白部分空间就浪费了。这些浪费的空间称为对象间的填充(padding)。

默认Java虚拟机中32位压缩指针可寻址到2的35次方个字节,即32GB地址空间(超过32GB则会关闭压缩指针)。

在对压缩指针解引用时,需将其左移3位,再加上一个固定偏移量,便可得到能寻址32GB地址空间的伪64位指针。

可通过配置刚提到的内存对齐选项(-XX:ObjectAlignmentInBytes)进步提升寻址范围。但是,这同时也可能增加对象间填充,导致压缩指针没有达到原本节省空间的效果。

举例来说,如规定每辆车都需从偶数车位号停起,那占据两个车位的小房车刚好,而对需三个车位的大房车仅是浪费一个车位。

但如规定需从4倍数号车位停起,那小房车则会浪费两车位,而大房车至多可能浪费三车位。

就算关闭压缩指针,Java虚拟机还是会进行内存对齐。内存对齐不仅存在于对象与对象间,也存在对象中的字段间。比如说,Java虚拟机要求long字段、double字段及非压缩指针状态下的引用字段地址为8的倍数。

字段内存对齐的其中一个原因是让字段只出现在同一CPU的缓存行。如字段不对齐,可能出现跨缓存行的字段,即该字段的读取可能需替换两个缓存行,而该字段的存储也会同时污染两个缓存行。这对程序执行效率都不好。

3 字段重排列

JVM重新分配字段先后顺序,以达到内存对齐。Java虚拟机中有三种排列方法(对应Java虚拟机选项-XX:FieldsAllocationStyle,默认值为1),但都遵循如下规则:

  • 如一个字段占C个字节,那该字段偏移量需对齐至NC偏移量指字段地址与对象的起始地址差值。long类仅有一个long类型实例字段。在使用压缩指针的64位虚拟机,尽管对象头12字节,该long类型字段的偏移量也只能是16,而中间空着的4字节便会被浪费掉。
  • 子类所继承字段的偏移量,要与父类对应字段的偏移量保持一致

JVM还会对齐子类字段的起始位置:

  • 使用压缩指针的64位虚拟机,子类第一个字段需对齐至4N
  • 关闭压缩指针的64位虚拟机,子类第一个字段需对齐至8N

两个类A和B,其中B继承A。A和B各自定义一个long类型的实例字段和一个int类型实例字段。

B类在启用压缩指针和未启用压缩指针时,各字段偏移量:

启用压缩指针,JVM将A类int字段置于long字段前,以填充因long字段对齐造成的4字节缺口。由于对象整体大小需对齐至8N,因此对象最后会有4字节空白填充。

当关闭压缩指针时,B类字段起始位置需对齐至8N。那B类字段前后各有4字节空白。

Java 8还引入新注释@Contended,解决对象字段之间的虚共享(false sharing)问题[2]。也影响字段排列。

4 虚共享

假设两线程分别访问同一对象中不同的volatile字段,逻辑上无共享内容,因此无需同步。但若这俩字段恰在同一缓存行,则对这些字段写操作会导致缓存行的写回,即造成了实质上的共享。

Java虚拟机会让不同@Contended字段处于独立缓存行,因此大量空间被浪费。可查阅Contended字段的内存布局。注意使用虚拟机选项-XX:-RestrictContended。

5 总结

本文介绍JVM构造对象的方式,所构造对象的大小,以及对象的内存布局。

new语句会被编译为new指令及对构造器调用。每个类的构造器都会直接或者间接调用父类构造器,且在同一实例中初始化相应字段。

JVM引入压缩指针,将原本64位指针压缩成32位。压缩指针要求JVM堆中对象的起始地址要对齐至8倍数,还会对每个类的字段进行重排列,使得字段也内存对齐。

使用JOL工具打印你工程中的类的字段分布:

 curl -L -O http://central.maven.org/maven2/org/openjdk/jol/jol-cli/0.9/jol-cli-0.9-full.jar
 java -cp jol-cli-0.9-full.jar org.openjdk.jol.Main internals java.lang.String

[1] https://wiki.openjdk.java.net/display/HotSpot/CompressedOops

[2] http://openjdk.java.net/jeps/142

相关文章

谷歌宣布:两年后所有32位应用程序将全部转换为64位

【CNMO新闻】一年前,谷歌向Android应用程序开发人员提供了关于2019年8月即将推出Google Play商店的新64位要求的提醒,现在它发布了有关其64位转换的更多细节。谷歌根据2017年1...

35个可以提高千倍效率的Java代码小技巧

代码优化 ,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是...

详解JVM中的Java对象(java的jvm是指)

文章目录对象的创建第一步:对象内存的分配指针碰撞(Bump the Pointer)空闲列表(Free List)内存分配的线程安全问题第二步:对象属性的初始化第三步:对象构造函数的执行对象的内存布局...

AMD 64位ARM处理器开卖:只是开发板

2014-08-01 09:00:00 [ 驱动之家 转载 ] 5月份,AMD公开展示了自家的第一颗ARM架构处理器“Opteron A1100”,代号“西雅图”(Seattle)。与之搭配的是一套开...

这10种分布式ID,太绝了!(分布式id的作用)

前言分布式ID,在我们日常的开发中,其实使用的挺多的。有很多业务场景在用,比如:分布式链路系统的trace_id单表中的主键Redis中分布式锁的key分库分表后表的id今天跟大家一起聊聊分布式ID的...

一台 Java 服务器可以跑多少个线程?

作者:新栋BOOK 来源:www.jianshu.com/p/f1930596947d 一台Java服务器能跑多少个线程?这个问题来自一次线上报警如下图,超过了我们的配置阈值。京东自研UMP监控分析打...