JVM(2):虚拟机对象探秘

对象的创建

对象的创建一般有四种方式:new关键字、反射、clone、序列化。
对象创建主要分三步:

  1. 检查类是否加载
      虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
  2. 分配内存
      主要有指针碰撞和空闲列表两种方式。指针碰撞是把内存指针向空闲空间移动与对象大小相等的距离来分配内存,这种主要适用于带有整理的垃圾收集器。空闲列表是维护一个列表记录哪些内存区域可以使用,在分配的时候从列表中找出一块分配,这种适用于不带整理功能的垃圾收集器。
      分配过程中还要考虑多个线程同时分配内存出现并发的问题。这个一般有两种方案:一是使用CAS机制保证更新操作的原子性;二是使用本地线程分配缓冲(TLAB)。
  3. 对象设置
      分配完内存之后需要进行对象设置,主要是把这个对象是哪个类的实例、对象的哈希码、对象的GC分代年龄、对象的锁信息等存放在对象的对象头里。

对象的内存布局

  对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
  对象头分成两部分:第一部分用于存储对象自身的运行时数据,如对象的哈希码、GC分代年龄、锁状态标志等,官方称之为‘MarkWord’。另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
  第三部分对象填充不是必须的,仅仅是占位的作用,用来保证对象的大小是8字节的整数倍。   

对象的访问定位

对象的访问主要两种方式:

  1. 句柄访问
      Java堆中会划出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。好处是对象移动时只用改动句柄中的实例数据地址,不用改变reference。
  2. 直接指针访问
      reference中存储的就是对象的地址。好处就是访问快,节省了一次指针定位的开销。hotspot使用直接指针访问。