JVM总结二:HotSpot对象创建的过程
对象创建的过程
当虚拟机遇到一个包含new的指令时,会进行一系列对象创建的操作。
- 检查常量池中是否包含有即将创建的这个对象所属类的符号引用。
- 若常量池中没有这个符号引用,那么表示这个类还没有被创建,抛出ClassNotFoundExecption异常。
- 若常量池中有这个符号引用,继续执行。
- 再检查这个符号引用所属的类是否已经被JVM加载。
- 若该类没有被加载,就找到该类的class文件,并加载进方法区。
- 若该类已经被加载,就准备为其对象分配内存。
- 根据方法区中该类的信息确定该类所需要的内存大小。
- 一个对象所需要的内存大小是这个对象所属类被定义完成就能确定的,并且一个类所生产的所有对象的内存大小一样的,JVM在一个类被加载进方法区的时候就知道该类所需要的大小空间。
- 从堆中划分块大小所对应的内存空间给新的对象,分配内存有两种方式:指针碰撞和空闲列表。
- 指正碰撞:如果JVM的垃圾回收集采用的是 “复制/标记-整理”那么堆中空闲的内存是完整的区域,并且空闲内存和已经使用的内存之间由一个指针标记。那么当为一个对象分配内存时,只需要移动指针即可。因此,这种在完整空闲区域上通过移动指针分配的方式叫做指针碰撞。
- 空闲列表:如果JVM的垃圾收集器采用的是标记-清除算法,那么堆中的空闲区域和已经使用的区域交错,因此需要一张“空闲列表”来记录堆中那些区域是空闲区域,并分配内存。综上所述:JVM采用哪种内存,取决于他们使用哪种垃圾回收机制。
- 为对象中的成员变量赋上初值;
- 设置对象头中的信息。
- 调用对象构造函数来完成初始化,此时,整个对象的创建就完成了。
对象内存模式
对象头
对象头中记录了对象在运行的过程中所需要使用的数据:哈希码,GC分代年龄,锁状态标记,线程持有的锁,偏向线程ID,偏向时间...
对象头还包含 类型指针 。用来确定这个对象是所属那个类。
如果对象是个数组,那么还有一个包含数组长度的值。
实例数据
实例数据部分就是成员变量的值,其中包含父类的成员变量和本类的成员变量。
对其补充
用于确保总字长为8个字节的整数倍。
HotSpot要求对象的长度必须是8的整数倍,由于对象头一定是8字节的整数倍,但是实例数据的长度是任意的,因此需要对齐补充字段长度来确保整个对象的总长度为8的整数倍。
访问对象的过程
- 句柄访问:堆中有一块叫做“句柄池”的内存空间,用于存放所有对象的地址和所有对象所属类的信息。引用类型的变量存放的是该对象在“句柄池中的地址”。访问对象时,首先通过引用类型的变量找到对象的句柄,然后根据句柄中的对象的地址再访问对象。
- 直接指针访问方式:引用类型的变量是直接存放的对象地址,从而不需要句柄池,通过引用直接访问对象。但对象所在的内存空间中需要额外的策略存储对象所属的类信息地址。
比较:
HotSpot采用的是直接指针方式访问对象,因为他只有一次寻址机会,从而性能提升了一倍;但是他需要提供额外的策略存储对象在方法区中的地址。