对象的创建
-
开始创建
在虚拟机遇见字节码的new指令时,会根据指令的参数定位到常量池的中的符号引用,并检查这个符号所代表的类是否被加载过。如果未被加载则先执行类的加载过程。 -
分配空间
分配空间的方式取决于垃圾收集器是否具有空间压缩整理能力。
如果垃圾收集器具有空间压缩整理能力,则java堆中的可用空间是连续的,只需要挪动标记已使用空间界限的指针,这种方法叫做“指针碰撞”。
如果垃圾收集器不具备空间压缩整理能力。例如基于标记清除算法的CMS收集器,大多是情况下就无法使用“指针碰撞”,就需要虚拟机维护一个可用内存的列表,在分配空间时从中找出一块足够大的空间划给对象实例,这种方法叫做“空闲列表” -
多线程下的同步
分配空间的过程不是线程安全的,需要进行额外的处理
虚拟机会采用乐观锁加失败重试的方法保证划分空间时对指针进行修改操作的原子性。
也可以为不同的线程划分不同的线程,即为每个线程在java堆中预先分配一小块内存,称为“本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)”,只有当本地缓存用完后分配新的缓存时才需要同步。可以使用-XX:+/-UseTLAB 来设置是否使用TLAB -
初始化
分配完成后会将会将分配到的空间(不包括对象头)初始化为零值,使用TLAB也可以提前到TLAB分配时进行。同时设置对象头,最后调用class文件中的< init >()方法即构造函数,将对象完整的构造出来
对象的内存布局
-
对象头
对象头的第一部分存储对象运行时的一些信息,因为要求所占空间较小同时要存储就多的数据,所以对存储数据的方式不做要求。在32位的hotspot虚拟机中未被锁定的情况下按如下格式存储数据:25bit : 对象的哈希码,java中希望如果一个对象计算过哈希码,就应该一直保持不变(不强制),这里存储的就是这种一致性哈希码,Object::hashcode()返回的就是一质性哈希码,重写的hashcode()方法不包括在内
4bit:分代年龄,与垃圾收集器的机制相关
1bit:偏向模式,对对象加同步锁时进行同步锁优化的标记位
2bit:标志位,标记对象所属状态,根据对象状态不同前面存储数据代表的含义也会变化,正常状态下为01,还可标记的状态为 00:被轻量级锁锁定, 10:被重量级锁锁定,11: 被GC标记
对象头的另一部分是类型指针,即对象指向它的类型元数据的指针(这一句是书中原话,这个类型元数据到底是不是所属类的字节码我还无法确定)。并非所有的虚拟机都会有类型指针,以句柄实现的引用类型不在对象头中保存类型指针,处此之外数组对象还会保存数组长度 -
实例数据: 即该对象中包括从父类继承下来的各种数据
-
对齐填充:用于保证对象起始地址为8的整数倍
对象的访问
对象通过引用类型访问。应用类型的实现方式通常有两种
- 句柄:在java堆中划分出一块区域作为句柄池,引用类型通过访问句柄来获取对象或类的数据所在地址,在通过这个地址进行实际的访问。优点是在垃圾回收需要移动对象时,可以方便的直接改变句柄中的地址,缺点是会增加额外的开销
- 指针:基本与C语言中的指针一致,hotspot虚拟机就采用这种方式