1 对象的创建
1. 虚拟机遇到 new 指令时首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查引用代表的类是否已被加载、解析和初始化过。如果没有,则执行类加载过程(第 7 章 虚拟机类加载机制)。
2. 加载检查通过后,分配内存(内存在类加载完成后便可完全确定)。
3. 内存分配完成后,虚拟机对对象进行必要的设置,如对象是哪个类的实例、如何找到类的元数据信息. 对象哈希码. 对象的 GC 分代年龄等 (都放在对象的对象头中)。
内存分配方式有:指针碰撞和空闲列表。使用哪种方式取决于堆是否规整(又由垃圾回收决定)。内存分配的线程安全问题:1.CAS 配上失败重试的方式保证原子性;2. 每个线程在 Java 堆中预先分配一小块内存,本地线程分配缓冲(TLAB)。分配完成后内存空间初始化为 0 值(不包括对象头)。
从虚拟机角度看,一个新的对象产生了,但从 java 程序视角看,对象创建才刚刚开始,因为 <init> 方法还没有执行,,所有字段为零。执行 new 指令之后会接着执行 <init> 方法(构造方法),进行初始化,这样一个真正可用的对象才算完成产生。
2 对象的内存布局
对象在内存中存储的布局可以分为 3 块区域:对象头、实例数据、对齐填充
①对象头含两部分(Header)
存储对象自身的 运行时数据,如哈希码、GC 分代年龄. 锁信息(锁标志,线程持有的锁, 偏向线程 ID)等。长度在 32 位和 64 位的虚拟机中,分别为 32bit、64bit, 官方称它为“Mark Word”。
类型指针,对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
备注:如果对象是 java 数组,对象头中还必须有一块记录数据长度的数据
②实例数据(InstanceData)
对象真正存储的有用信息,也是程序中定义的各种类型的字段内容, 包括父类继承的都记录下来,存储顺序受到虚拟机分配策略参数和字段在 Java 源码中的顺序的影响。
③对齐填充(Padding)
由于 HotSpot 虚拟机要求对象的起始地址必须是 8 字节的整数倍,通俗的说,就是对象大小必须是 8 字节的整数倍。对象头正好是 8 字节的倍数。当实例数据部分没有对齐时,需要通过对齐填充来补全。
3 对象的访问定位
Java 程序通过栈上的 reference 数据来操作堆上的具体对象。不同虚拟机实现的对象访问方式会有所不同,目前主流的访问方式有两种:使用句柄和直接指针。
使用句柄: 堆中划分出一块内存作为句柄池,reference 存储对象的句柄地址 是间接访问,优点是 reference 中存储的是稳定的句柄地址,对象移动(垃圾会收时)时只会改变句柄中的实例数据指针。
使用直接指针 是直接访问,节省了一次指针定位的开销, 优点就是速度快。(HotSpot)