深入拆解 Java 虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
87446 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 40 讲
模块四:黑科技 (3讲)
深入拆解 Java 虚拟机
15
15
1.0x
00:00/00:00
登录|注册

10 | Java对象的内存布局

虚共享问题
@Contended注释
排列方法
内存对齐
压缩指针原理
64位虚拟机中的额外开销
类型指针
标记字段
显式调用父类带参数的构造器
子类构造器需要调用父类构造器
自动添加无参数构造器
JOL工具的使用
字段重排列
压缩指针
对象头
构造器的约束
父类构造器的调用
Unsafe.allocateInstance方法
反序列化
Object.clone方法
反射机制
new语句
实践
对象的内存布局
构造器的调用
新建对象的方式
Java对象的内存布局

该思维导图由 AI 生成,仅供参考

在 Java 程序中,我们拥有多种新建对象的方式。除了最为常见的 new 语句之外,我们还可以通过反射机制、Object.clone 方法、反序列化以及 Unsafe.allocateInstance 方法来新建对象。
其中,Object.clone 方法和反序列化通过直接复制已有的数据,来初始化新建对象的实例字段。Unsafe.allocateInstance 方法则没有初始化实例字段,而 new 语句和反射机制,则是通过调用构造器来初始化实例字段。
以 new 语句为例,它编译而成的字节码将包含用来请求内存的 new 指令,以及用来调用构造器的 invokespecial 指令。
// Foo foo = new Foo(); 编译而成的字节码
0 new Foo
3 dup
4 invokespecial Foo()
7 astore_1
提到构造器,就不得不提到 Java 对构造器的诸多约束。首先,如果一个类没有定义任何构造器的话, Java 编译器会自动添加一个无参数的构造器。
// Foo类构造器会调用其父类Object的构造器
public Foo();
0 aload_0 [this]
1 invokespecial java.lang.Object() [8]
4 return
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Java对象的内存布局对于Java程序员来说至关重要。本文介绍了在Java程序中新建对象的多种方式,以及对象的内存布局中的压缩指针和字段重排列。在64位Java虚拟机中,对象头的大小为16个字节,而压缩指针的引入可以将对象头的大小降至12个字节,从而减少对象的内存占用。压缩指针的原理类似于将停车位号转换为车号,通过内存对齐来实现。另外,字段重排列是为了达到内存对齐的目的,子类继承字段的偏移量需要与父类对应字段的偏移量保持一致。这些概念对于理解Java对象的内存布局和优化内存使用具有重要意义。 文章还介绍了启用和关闭压缩指针时,对象字段的分布情况,以及对虚共享问题的解决方案。压缩指针的启用可以减少对象内存占用,而关闭压缩指针时,对象字段的起始位置需对齐至8N,导致字段前后各有4字节的空白。此外,文章还提到了新的注释@Contended的引入,用于解决对象字段之间的虚共享问题,但也会影响字段的排列。 总的来说,本文详细介绍了Java虚拟机构造对象的方式、对象的大小和内存布局,以及压缩指针和字段重排列的相关概念。读者可以通过JOL工具来打印工程中类的字段分布情况,从而更好地理解和优化内存使用。 综上所述,本文内容涵盖了Java对象内存布局的重要概念和优化方法,对于Java程序员来说具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入拆解 Java 虚拟机》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(56)

  • 最新
  • 精选
  • life is short, enjoy mor...
    对象头 每个对象都有一个对象头,对象头包括两部分,标记信息和类型指针。 标记信息包括哈希值,锁信息,GC信息。类型指针指向这个对象的class。 两个信息分别占用8个字节,所以每个对象的额外内存为16个字节。很消耗内存。 压缩指针 为了减少类型指针的内存占用,将64位指针压缩至32位,进而节约内存。之前64位寻址,寻的是字节。现在32位寻址,寻的是变量。再加上内存对齐(补齐为8的倍数),可以每次寻变量都以一定的规则寻找,并且一定可以找得到。 内存对齐 内存对齐的另一个好处是,使得CPU缓存行可以更好的实施。保证每个变量都只出现在一条缓存行中,不会出现跨行缓存。提高程序的执行效率。 字段重排序 其实就是更好的执行内存对齐标准,会调整字段在内存中的分布,达到方便寻址和节省空间的目的。 虚共享 当两个线程分别访问一个对象中的不同volatile字段,理论上是不涉及变量共享和同步要求的。但是如果两个volatile字段处于同一个CPU缓存行中,对其中一个volatile字段的写操作,会导致整个缓存行的写回和读取操作,进而影响到了另一个volatile变量,也就是实际上的共享问题。 @Contented注解 该注解就是用来解决虚共享问题的,被该注解标识的变量,会独占一个CPU缓存行。但也因此浪费了大量的内存空间。

    作者回复: 赞总结!

    2018-10-11
    2
    79
  • 神佑小鹿
    在默认情况下,Java 虚拟机中的 32 位压缩指针可以寻址到 2 的 35 次方个字节,也就是 32GB 的地址空间(超过 32GB 则会关闭压缩指针)。 这里为啥是 35 ????

    作者回复: 以8字节为单位,跟ObjectAlignmentInBytes有关

    2019-11-27
    3
    8
  • 一个坏人
    老师好,请教一下:“自动内存管理系统为什么要求对象的大小必须是8字节的整数倍?”,即内存对齐的根本原因在于?

    作者回复: 在某些体系架构上,不对齐的话内存读写会报错。 在X86_64上,一个是为了让字段也能对齐,这样就不会出现字段横跨两个缓存行的情况,另一个原因更像个副作用,就是对象地址最后三位一直是0,JVM利用这个特性来实现压缩指针,也可以用这三位来记录一些额外信息

    2018-08-25
    3
    7
  • 大能猫
    最近研究String时遇到一个跟Java内存相关的问题:常量池里到底有没有存放对象? 常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference); 如果常量池里有一个“hello”的字面量,这个字面量算是一个对象吗?如果不算对象,那么它所指向的对象又存放在哪里呢

    作者回复: String literal指向的对象存放在JVM的String pool里。

    2018-08-14
    6
  • everyok22
    你文章里说: 64位的JVM中,不采用压缩指针的方式,标记字段与类型指针分别占用8个字节,而采用了压缩指针标记字段与类型指针都会压成32位(8字节)那对象头不是只占用8个字节么,为什么你说是12个字节

    作者回复: 标记字段没有被压缩。

    2018-08-22
    4
  • 周仕林
    对象头的组成如果阅读过周志明的JVM虚拟机会发现作者说的有一些有失偏颇,对象头的组成是对象运行信息,类型指针(如果对象访问采用直接指针),数组长度(如果对象是数组)

    作者回复: 周老师书上的是提炼之后的抽象说法。 HotSpot的对象头一直是mark word(标记字段)和类型指针。如果对源代码有兴趣可以查看OpenJDK源代码目录下,src/hotspot/share/oops/oop.hpp里的class oopDesc。数组对象头是同目录下的arrayOop.hpp里的class arrayOopDesc

    2018-08-21
    3
  • 贾智文
    默认情况下32位可以寻址2的35次,应该是因为地址是32位乘以2的三次(默认对其为8),那么如果不采用压缩指针,能够寻址的范围应该是2的64次对吧。然后之前模糊的地方就是觉得两个寻址范围并不一致,是不是可以这么理解并没有通过压缩指针让两个寻址范围一致,而是通过压缩指针放大了32位的寻址空间使它够用了

    作者回复: 对的

    2018-08-15
    2
  • 熊猫酒仙
    接触过C/C++的内存字节对齐,就比较好理解本章内容了。希望老师后面讲讲java内存在并发上的相关机制,譬如搞不懂的内存障是怎么实现的!

    作者回复: 底层的内存屏障,比如说mfence,lock指令,是用来防止指令重排布的。Java里的内存屏障除了生成上述底层指令外,还会限制即时编译器对内存访问的重排序。之后我会讲Java内存模型。

    2018-08-14
    2
  • 贾智文
    有一点想不明白,既然内存对齐是八位而不是举例的两位为什么空间只是从64位变成32而不是从64变成8

    作者回复: 不是很理解你的问题。 对象间需要内存对齐至8字节。64位和32位对应8字节和4字节。

    2018-08-15
    2
    1
  • 槑·先生
    在极客时间买了不少课程了,这个系列算是难读的。
    2019-06-15
    25
    69
收起评论
显示
设置
留言
56
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部