深入分析虚拟机在Java堆中对象分配、布局和访问的全过程

时间:2022-12-24 15:56:37

对象的创建

—>虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池(方法区中)中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,必须先执行类的加载过程。

—>指针碰撞:如果Java堆中的内存是绝对规整的,所有用过的内存都放在一起,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式成为指针碰撞(Bump the Pointer)。
—>空闲列表:如果Java堆中的内存不是规整的,已使用的内存和空间的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,用于存储哪些内存块是可用的,在分配的时候从列表中找到一快足够大的空闲划分给对象实例,并更新列表上的记录,这种分配方式称为空闲列表(Free List)。


对象的内存布局

–>对象在内存存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

—>对象头包括两部分信息:第一个部分存储:对象自身的运行时数据,如HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称“Mark Word”。第二部分是类型指针,是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个数组,那在对象头中还必须有一块用于记录数组长度的数据。

—>实例数据:是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录下来。

—>对齐填充:仅起占位符的作用。


对象的访问定位

–>Java程序需要通过栈上的reference数据来操作堆上的具体对象。
目前主流的对象的访问方式有使用句柄直接指针两种。

  • 如果使用句柄访问的话,Java堆中将会划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
    深入分析虚拟机在Java堆中对象分配、布局和访问的全过程

  • 如果使用直接指针访问,Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
    深入分析虚拟机在Java堆中对象分配、布局和访问的全过程

  • 使用句柄访问的好处是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
  • 使用直接指针访问的好处就是速速更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多也是一项非常可观的执行成本。

信息来自《深入理解Java虚拟机:JVM高级特性与最佳实践》(第二版)