深入拆解Java虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
立即订阅
28017 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么我们要学习Java虚拟机?
免费
模块一:Java虚拟机基本原理 (12讲)
01 | Java代码是怎么运行的?
02 | Java的基本类型
03 | Java虚拟机是如何加载Java类的?
04 | JVM是如何执行方法调用的?(上)
05 | JVM是如何执行方法调用的?(下)
06 | JVM是如何处理异常的?
07 | JVM是如何实现反射的?
08 | JVM是怎么实现invokedynamic的?(上)
09 | JVM是怎么实现invokedynamic的?(下)
10 | Java对象的内存布局
11 | 垃圾回收(上)
12 | 垃圾回收(下)
模块二:高效编译 (12讲)
【工具篇】 常用工具介绍
13 | Java内存模型
14 | Java虚拟机是怎么实现synchronized的?
15 | Java语法糖与Java编译器
16 | 即时编译(上)
17 | 即时编译(下)
18 | 即时编译器的中间表达形式
19 | Java字节码(基础篇)
20 | 方法内联(上)
21 | 方法内联(下)
22 | HotSpot虚拟机的intrinsic
23 | 逃逸分析
模块三:代码优化 (10讲)
24 | 字段访问相关优化
25 | 循环优化
26 | 向量化
27 | 注解处理器
28 | 基准测试框架JMH(上)
29 | 基准测试框架JMH(下)
30 | Java虚拟机的监控及诊断工具(命令行篇)
31 | Java虚拟机的监控及诊断工具(GUI篇)
32 | JNI的运行机制
33 | Java Agent与字节码注入
模块四:黑科技 (3讲)
34 | Graal:用Java编译Java
35 | Truffle:语言实现框架
36 | SubstrateVM:AOT编译框架
尾声 (1讲)
尾声 | 道阻且长,努力加餐
深入拆解Java虚拟机
登录|注册

10 | Java对象的内存布局

郑雨迪 2018-08-13
在 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/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Java虚拟机》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(36)

  • godtrue
    小结
    1:Java中创建对象的方式

    1-1:new -通过调用构造器来初始化实例字段

    1-2:反射-通过调用构造器来初始化实例字段

    1-3:Object.clone-通过直接复制已有的数据,来初始化新建对象的实例字段

    1-4:反序列化-通过直接复制已有的数据,来初始化新建对象的实例字段

    1-5:Unsafe.allocateInstance-没有初始化对象的实例字段

    2:Java对象的空间占用

    2-1:通过new创建的对象,涵盖了它所有父类中的对象实例的字段

    2-2:对象头,由标记字段和类型指针构成

    2-3:标记字段,用于存储Java虚拟机有关该对象的运行数据,比如:哈希码、GC信息、锁信息等

    2-4:类型指针,用于指向该对象的类

    2-5:此对象的实例字段对应的内存空间

    3:压缩指针
    JVM的内存空间有限且昂贵,所以,能缩减的就缩减,通过一定的算法改进压缩类型指针的空间后仍可以寻址到对象的实例对应的类,所以,就采用了

    4:字段重排
    意思是JVM会重新分配字段的位置,和我们Java源码中属性声明的位置存在差异,猜想Java编译器编译后的字节码是没有改变源码中字段声明的位置的,这样做是为了更好的实现内存对齐,内存对齐本质上会浪费一定的内存空间,不过可以减少内存行的读取次数,通过一消一涨的比对发现这样对于JVM的性能有一定的提高,所以,也就使用了这种方式,浪费点空间能提高性能也是值得的

    疑问❓
    1:为什么一个子类即使无法访问父类的私有实例字段,或者子类实例字段隐藏了父类的同名实例字段,子类的实例还是会为这些父类实例字段分配内存呢?
    另外,如果采用指针指向的方式定位父类实例的内容是否能更节省内存空间?

    2:五种创建对象的方式,通过new指令新建出来的对象,他的内存其实涵盖了所有父类中的实例字段,其他的方式是怎样的哪?

    2018-08-13
    1
    39
  • 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
    21
  • amourling
    作者大大辛苦了,货很干,搭配《深入理解java虚拟机》会很香
    2018-08-13
    12
  • 槑·先生
    在极客时间买了不少课程了,这个系列算是难读的。
    2019-06-15
    10
  • 清歌
    讲内存布局没有图示。如果能配一些图来说明就更清晰了,纯文字不直观
    2018-11-14
    9
  • 倔强
    也就是说默认情况下,小于32G的堆内存中的对象引用为4个字节,一旦堆内存大于32G,对象引用为8个字节
    2018-08-14
    5
  • 三木子
    有一个小白问题,new一个对象(继承一个类)会调用父类构造器,这个可以理解,因为对象可能调用父类方法。那么为什么new对象会调用到object呢?这有什么用意吗?
    2018-08-13
    1
    5
  • 樱小路依然
    老师,问个问题,你在用小车举例的时候说,规定从 4 的倍数停起,那么小车可能会浪费2个车位,大车可能会浪费3个车位,那 XX:ObjectAlignmentInBytes 的默认值是 8,岂不是小车可能会浪费6个车位,大车可能会浪费7个车位?为什么这个 XX:ObjectAlignmentInBytes 的默认值不设置成2,而是设置成8了呢?
    2019-06-19
    1
    3
  • Mr.钧👻
    想请教老师大大几个问题:
    1、什么是CUP缓存行?
    2、如果跨缓存行的字段,为什么会降低执行效率?是因为某些读取程序,一行一行的读效率较高?还是因为以行分割呢?
    3、明显启用压缩指针,性能更高,但是为什么还会在64位情况下,不启用压缩指针的情况呢? 是因为CPU运行速度更快,可以护士不压缩指针导致的内存浪费吗?
    2018-10-16
    2
  • 发条橙子 。
    老师, 对象头中的类型指针只是为了指该对象的类 , 使用了压缩指针还有32位 。可以有32g的地址空间, 一个类能用到 32 G的地址空间么?????
    2019-03-22
    1
  • xlogic
    字段重排列

    其一,如果一个字段占据 C 个字节, 那么该字段的偏移量需要对齐至 NC。这里的偏移量指的是字段地址与对象的起始地址差值

    以 long 类为例,它仅有一个 long 类型的实例字段。在使用了压缩指针的 64 位虚拟机中,尽管对象头的大小为 12 个字节,该 long 类型字段的偏移量也只能是 16,而中间空着的 4 个字节便会被浪费掉。

    个人理解:1. 应该是 Long 类型;2. 因为 long 字段的占 8 个字节,所以偏移量是 N8,比12大的最接近的数就是 16,所以偏移量就是16,也就是说字段与对象的起始位置差是16。
    2018-09-29
    1
  • Geek_987169
    老师,请教您几个问题
    1:每个类都有一个对应的class对象,那么这class对象是什么时候生成的,存储jvm的哪个区域?
    2:类实例对象object header中的类型指针其实就是指向该类所属class的对象的指针吗?
    3:class对象的内存结构又是什么样子的呢?类似于普通Java实例对象吗?
    ps:这个指针压缩的原理有些困扰到我了。。。求解惑!!!
    2018-09-22
    1
  • 一个坏人
    老师好,请教一下:“自动内存管理系统为什么要求对象的大小必须是8字节的整数倍?”,即内存对齐的根本原因在于?

    作者回复: 在某些体系架构上,不对齐的话内存读写会报错。

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

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

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

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

    作者回复: 不是很理解你的问题。

    对象间需要内存对齐至8字节。64位和32位对应8字节和4字节。

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

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

    2018-08-14
    1
  • 老白
    “但是如果规定需要从 4 的倍数号车位停起,那么小房车则会浪费两个车位,而大房车至多可能浪费三个车位”,大房车占用三个车位,那么为什么会至多浪费三个车位?我理解是一个小房车跟着一个大房车,这样8个车位会浪费三个。但是一个小房车跟着一个小房车还会浪费四个吧?这句话是不通顺,还是说有其他解释?
    2019-12-07
  • 放个屁臭到了自己
    在默认情况下,Java 虚拟机中的 32 位压缩指针可以寻址到 2 的 35 次方个字节,也就是 32GB 的地址空间(超过 32GB 则会关闭压缩指针)。


    这里为啥是 35 ????

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

    2019-11-27
  • 长脖子树
    jol 真的是很好用的一个工具
    2019-11-05
  • 木兮
    字段内存对齐的其中一个原因,是让字段只出现在同一 CPU 的缓存行中。老师,首先缓存行是个什么概念?能讲解具象一点吗?然后为什么字段不内存对齐就会出现在跨缓存行里面,一个字段还可以拆分存到多个缓存行里吗?
    2019-09-25
收起评论
36
返回
顶部