对象的创建
- 当JVM遇到一个new指令时,首先会去检查这个指令参数是否能在常量池中定位到一个类的符号引用,并检查这个类的符号引用代表的类是否已被加载、解析和初始化过,如果没有则必须先执行加载过程。
- 类加载检查通过之后,接下来虚拟机将为新生的对象分配内存,对象内存的大小在类加载完成之后便可完全确定。内存的分配方式包含指针碰撞(java堆内存是规整的)或空闲列表(java堆内存是不规整的)。
- 内存分配完之后,虚拟机必须要将分配到的内存空间(不包括对象头)都初始化为零。
- 接下来对对象进行必要的设置,例如对象属于哪个类的实例、如何才能找到对象的元数据信息、对象的哈希码(实际对象的哈希码会延后到真正调用Object::hashCode()方法时计算)、对象的GC分带年龄等信息,这些信息存放在对象的对象头之中。根据当前虚拟机运行的状态的不同。如是否设置偏向锁,对象头会有不同的设置。
- 以上工作完成之后,对于虚拟机来说,一个新的对象已经完成。当时从java的角度来看,对象创建刚刚开始–构造函数,级Class文件中的()方法还没有执行,多有默认字段都为零。一般情况下,new指令之后都会接着执行()方法,按照程序员的意愿对对象进行初始化,这样一个真正的对象才算被构造出来。
对象的内存布局
对象在堆里存储的布局可以划分为三部分:对象头、实例数据和对其填充。
- 对象头信息:包含两类,一类是存储对象运行时数据,如哈希码、GC年龄分段、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分是类型指针,指向它的类型元数据的指针,jvm通过这个指针来确定是哪个类的实例。如果是java对象数组,对象头中还必须有一块用于记录数组长度的数据。
- 实例数据:对象存储的真正有效的信息,无论是父类还是子类定义的字段都会记录下来
- 对其填充:HotSpot虚拟机自动内存管理要求对象的起始地址必须是8字节的整数倍
对象访问定位
reference类型是一个指向对象的引用,访问具体对象的方式主要有句柄和直接指针。
- 句柄访问:Java堆可能会划分出一块内存来作为句柄池,rederence中存储的就是对象的句柄地址,而句柄地址中包含对象的实例数据与类型数据各自具体的地址信息。
- 直接指针: reference中存储的是对象地址、如果访问对象本身的话,只需要一次间接访问开销。