JVM 对象的内存分配和访问
文章目录Java对象的内存分配对象的创建流程类加载检查对象内存分配对象的内存布局对象头区域实例数据区域填充对齐区域对象的访问定位方式句柄访问直接指针访问Java对象的内存分配对象的创建流程虚拟机收到new指令触发。类加载检查:会判断类是否已经被加载,如果没有被加载则需要先执行类加载流程,对象所需内存大小在类加载完后可以完全确定。为对象分配内存,从堆中划分出一块确定大小的内存。内存分配完后,虚拟机需
·
JVM 对象的内存分配和访问
对象的实例化方式
- new
- Class#newInstance()
- clone()
- 反序列化
对象的创建过程
- 虚拟机收到new指令触发。
- 类加载检查:首先检查该类是否被加载。如果没有加载,则进行类的加载过程;如果已经加载,则在堆中分配内存。对象所需的内存的大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。这个指令完毕后,将指向实例对象的引用变量压入虚拟机栈栈顶。
- 对象分配内存:从堆中划分出一块确定大小的内存。
- 内存空间初始化:内存分配完后,虚拟机需要将分配到的内存空间初始化为零值(如:int值为0,boolean值为false等),保证了对象的实例字段在Java代码中可以直接使用。
- 为对象进行必要的设置:虚拟机为对象进行设置,如设置对象属于哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象头中。
- 从虚拟机的角度来看,一个新的对象已经创建完毕。但从Java程序的角度来看,对象创建才刚开始,所有的字段还是零值,所以需要程序员进行初始化操作,这样一个真正可用的对象才算完全产生出来。
类加载检查
- 检查 new 指令单参数是否能在常量池中定位到一个类的符号引用。
- 检查类是否被加载、解析、初始化过。
对象内存分配方式
虚拟机为新对象分配内存,从堆中划出一块确定大小的内存,因为对象所需内存的大小在类加载完后可以完全确定。
堆内存是否规整:
- 堆内存规整:已使用的内存在一边,未使用内存在另一边。
- 堆内存不规整:已使用内存和未使用相互交错。
堆内存是否规整是由垃圾收集器是否带有压缩整理功能决定的。
内存分配方式:
分配方式的选择 取决于 Java
堆内存是否规整:
- 指针碰撞方式:
- 堆内存绝对规整。
- 分配过程:将已使用内存和为使用内存之间放一个分界点的指针,分配内存时,指针会向未使用内存方向移动,移动一段与对象大小相等的距离。
- 空闲列表:
- 堆内存不规整。
- 分配过程:虚拟机内部维护了一个记录可用内存块的列表,在分配时从列表找一块足够大的空间划分给对象实例,并更新列表上的记录。
Java
堆是否规整 由所采用的垃圾收集器是否带有压缩整理功能决定
线程安全问题
对象创建在虚拟机中是非常频繁的操作,即使仅仅修改一个指针所指向的位置,在并发情况下也会引起线程不安全。
解决线程安全问题有两种方案:
- 同步处理分配内存空间的行为。虚拟机采用
CAS
+ 失败重试的方式 保证更新操作的原子性。 - 把内存分配行为 按照线程 划分在不同的内存空间进行。本地线程分配缓冲(Thread Local Allocation Buffer ,TLAB)技术。
对象的内存布局
在Java
虚拟机(HotSpot
)中,对象在 Java
内存中的 存储布局 可分为三块:
- 对象头 存储区域
- 实例数据 存储区域
- 对齐填充 存储区域
对象头区域:
- 存储对象自身的运行时数据,如:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳。
- 存储对象类型指针,即对象指向类元数据的指针,JVM可以确定这个对象属于哪个类的实例。
- 如果是数组,对象头中还有一块记录数组长度的数据。
实例数据区域:
- 代码中定义的字段内容。
对齐填充区域:
- 占位符。
- 非必须。
说明:占位符起占位作用,因为对象的大小必须是8字节的整数倍,而因HotSpot VM的要求对象起始地址必须是8字节的整数倍,且对象头部分正好是8字节的倍数。因此,当对象实例数据部分没有对齐时(即对象的大小不是8字节的整数倍),就需要通过对齐填充来补全。
对象的访问定位
创建对象后,Java程序需要通过栈上的reference数据访问堆中的对象实例。
目前对象访问方式有两种:
- 句柄访问
- 直接指针访问
目前大部分JVM虚拟机,包括HotSpot虚拟机采用的是直接指针访问。
句柄访问
说明:
- 在Java堆中划分一块内存作为句柄池。
- 栈中reference数据存储的是对象的句柄地址。
- 句柄中包含对象实例数据和类型数据的地址。
优点:
- reference存储的稳定的句柄地址,在对象被移动时只会改变句柄中实例数据指针,reference不需要做修改。
场景:
- 适合频繁移动对象地址。
直接指针访问
说明:
- 栈中reference数据存储的是对象地址。
- 对象类型数据的指针直接存放在对象中的对象头。
优点:
- 速度快。
场景:
- 适合频繁访问对象。
更多推荐
已为社区贡献5条内容
所有评论(0)