7.2对象布局设计的原理
目录介绍
- 02.对象的内存布局
- 2.1 对象头
- 2.2 实例数据
- 2.3 对齐填充
- 03.Java对象创建过程
- 3.1 创建类加载过程
- 3.2 对象的创建
- 3.3 对象的内存布局
- 04.JVM处理对象流程
- 4.1 如何创建
- 4.2 最佳存储模型
- 4.3 如何访问
02.对象的内存布局
- 分为3个区域:
- 对象头,实例数据,对齐填充。
image
- 如何查看对象布局
- https://zhuanlan.zhihu.com/p/151856103?from_voters_page=true
2.1 对象头
- 包括两部分信息
- 第一部分:对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别为32 bit和64 bit,官方称它为“Mark Word”。
- 第二部分:类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个java数组,那在对象头中还必须有一块用于记录数组长度的数据。
- 在对象头中有两类信息:标志信息(Mark Word)和类型指针(Kclass Pointer)
- 1.标识信息用来存放对象一些固有属性的状态,这些属性从对象创建就有,而不是 Java 的使用者定义的:
- 哈希码:对象的唯一标识符
- 对象的分代年龄:与垃圾回收有关
- 线程持有的锁
- 锁的状态
- 偏向线程 ID、偏向时间戳
- 数组长度:如果该对象是数组,会有数组长度信息
- 2.类型指针是指向方法区中类元信息的指针。
- 1.标识信息用来存放对象一些固有属性的状态,这些属性从对象创建就有,而不是 Java 的使用者定义的:
2.2 实例数据
- 实例的信息存放的是一些对Java使用者真正有效的信息
- 也就是类中定义的各个字段,其中还包括从父类继承的字段。hotspot把相同宽度的类型分配在一起。
2.3 对齐填充
- 对齐填充不是必然存在的。
- HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是说对象的大小必须是8字节的整数倍。
- 而对象头部分正好是8字节的整数倍。因此,当对象实例数据部分没有对齐时,就需要通过对其补充来补全了。
03.Java对象创建过程
3.1 创建类加载过程
- Person p = new Person()请写一下类的加载过程?
1).因为new用到了Person.class,所以会先找到Person.class文件,并加载到内存中; 2).执行该类中的static代码块,如果有的话,给Person.class类进行初始化; 3).在堆内存中开辟空间分配内存地址; 4).在堆内存中建立对象的特有属性,并进行默认初始化; 5).对属性进行显示初始化; 6).对对象进行构造代码块初始化; 7).对对象进行与之对应的构造函数进行初始化; 8).将内存地址付给栈内存中的p变量
3.2 对象的创建
- Java对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。
- 1.类加载检查
- 2.分配内存
- 3.初始化零值
- 4.设置对象头
- 5.执行init方法
- ①类加载检查:
- 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
- ②分配内存:
- 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
- 内存分配的两种方式:
- 选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"("标记-压缩"),值得注意的是,复制算法内存也是规整的
- 内存分配并发问题
- 在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
- CAS+失败重试:
- CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
- TLAB:
- 为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配
- ③初始化零值:
- 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
- ④设置对象头:
- 初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。
- 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
- ⑤执行 init 方法:
- 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,
<init>
方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行<init>
方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
- 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,
3.3 对象的内存布局
- 在 Hotspot 虚拟机中,对象在内存中的布局可以分为3快区域:
- 对象头、实例数据和对齐填充。
- Hotspot虚拟机的对象头包括两部分信息
- 第一部分用于存储对象自身的自身运行时数据(哈希吗、GC分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
- 实例数据部分是对象真正存储的有效信息
- 也是在程序中所定义的各种类型的字段内容。
- 对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。
- 因为Hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
04.JVM处理对象流程
- 从jvm处理对象的流程来看,大概分成三步骤:
- 1.如何创建。2.什么是最佳存储模型。3.如何访问。
4.3 如何访问
对象创建起来之后,就会在虚拟机栈中维护一个本地变量表,用于存储基础类型和基础类型的值,引用类型与引用类型的值。
其中引用类型的值就是堆中对象地址。如何引用堆中地址有两种方式:
- 句柄:在堆中维护一个句柄池,句柄中包含了对象地址,当对象改变的时候,只需改变句柄,不需要改变栈中本地变量表的引用
- 直接指针:对象的地址直接存储在栈中,这样做的好处就是访问速度变快(Hotspot采用该方式)
【Deprecated】Java | Object obj = new Object()占用多少字节?
- https://juejin.cn/post/6887594129405149197
Java对象的内存布局
- https://time.geekbang.org/column/article/13081