对象的创建
当Java虚拟机遇到一条new字节码指令时,首先检查指令的参数是否在常量池中能够定位到,并且检查这个符号引用代表的类是否已经被加载、解析、初始化过,如果没有那就必须先执行相应的类加载过程。
接下来会给新生的对象分配空间,分配策略根据是否带有空间压缩整理功能分为两种:
- 指针碰撞
空闲的区域是连续的,用一个指针来确定空闲与非空闲的边界 - 空闲列表
空闲的区域不是连续的,维护一个列表来确定空闲区域的位置
同时由于新对象的创建是一个非常频繁的操作,需要解决并发问题,解决办法也有两个:
- 进程共享内存空间,采用CAS配上失败重试的策略保证操作的原子性
- 把内存分配的动作按线程划分在不同的空间中进行,每个线程拥有一小块独立的内存叫做本地线程分配缓冲(Thread Local Allocation Buffer TLAB)
内存分配结束之后需要讲分配到的内存空间(不包括对象头)都初始化为零,然后对对象进行必要的设置,也就是初始化对象头(包括这个对象是哪个类的实例、如何才能找到类的元信息、对象的哈希码、对象的GC分代年龄信息等)。然后再开始执行对象的构造函数,给属性赋值。
对象的内存布局
对象在堆内存中分为三个部分:对象头、实例数据和对齐填充。
对象头
对象头有两个部分组成:Mark Word和类型指针。
Mark Word根据标志位的标志不同会代表不同的内容:
其中在轻量级锁定以及重量级锁定的时候,在上锁之前会把对象头中的Mark Word复制一份到另一块内存区域进行存储。
类型指针即对象指向它类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。如果该对象还是一个数组,拿在对象头中还必须有一块用于标记数组长度的数据。
实例数据
HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinart Object Pointers)相同宽度的字段总是被分配到一起存放,在满足这个前提下,在父类中定义的变量会出现在子类前。
对齐补充
用于占位使对象大小成为8字节的倍数
对象的访问定位
主流的方式有两种:
-
句柄访问,好处是对象移动或者删除之后不需要改变本地变量表中的指针,坏处是会多一步寻址的开销
-
直接指针,好处是减少寻址的开销,坏处是需要更改本地变量表的指针