HotSpot是最常用的虚拟机,我们来看一下HotSpot中对象创建、布局、访问的过程
一、对象的创建
- 类加载:当java虚拟机遇到了字节码new指令时,会首先检查指令的参数能否在常量池中定位到类的符号引用,检查类是否已经被加载,如果没有则先加载类
- 给对象分配内存:类加载完成后就可以确定对象所需内存大小,接下来从堆中给对象分配一块内存。当使用Serial、ParNew等带有压缩整理过程的收集器时,可以用指针碰撞算法分配内存,如果是CMS收集器的话,则要用空闲链表
- 保证分配时线程安全:有两种是实现 1:CAS实现同步 2:本地线程分配缓冲(TLAB),用-XX:+/-UseTLAB设定
- 初始化:1:设置对象头 2:初始化内存为零 3:执行<init>()方法
二、对象的内存布局
HotSpot中,对象内存布局可以分为三个部分:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)
1.对象头
- Mark Word
根据32还是64位虚拟机,Mark Word也分32、64位。
用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
- 类型指针
指向类型元数据,虚拟机通过这个指针查找对象是哪个类的实例(不是所有虚拟机都实现了这个)。 - 如果是数组的话还要有一块记录数组的长度
2.实例数据
这部好像没啥特别的 QAQ 实例数据就是保存类的字段
实例数据部分是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。
3.对齐填充
HotSpot要求对象起始地址必须是8字节整数倍,如果实例数据不满足要求的话需要填充
三、对象访问定位
Java程序会通过栈上的reference数据来操作堆上的具体对象,对于reference具体如何定位访问堆上的对象,虚拟机规范没有明确要求,主流的实现方式有两种。HotSpot是第二种方式
- 句柄:堆中专门划出一块句柄池,reference存储句柄地址,句柄存储了实例、类型的地址。
- 这样做的好处是在垃圾回收移动了对象时,只需改变句柄内容就好了,而不需要去改变reference的内容
- 坏处就是访问对象时需要两次访问内存
- 直接访问:reference存储对象地址,对象头中存储类型地址
- 好处就是访问对象只需一次访存(不使用Shenandoah收集器的话)
- 坏处是垃圾回收移动了对象时,需要改变reference存储的内容,另外,在访问类型数据时也需要两次访存