JVM——Java对象的创建、内存布局和访问定位

Java对象的创建、内存布局和访问定位

注:本文所讨论的虚拟机都是指HotSpot

对象的创建

下面尝试用一张图说清楚这个过程,整个过程中需要注意的点在图中黄色背景的方框中进行说明。

在JVM的层面上,对象的创建主要由类加载检查为对象分配内存将分配的内存空间初始化为零值对对象进行必要的设置这几部构成。

JVM——Java对象的创建、内存布局和访问定位

对象的内存布局

对象在内存中的布局分为以下三个部分:

JVM——Java对象的创建、内存布局和访问定位

1.对象头

对象头包含两部分信息:

Mark Word:存储对象自身运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。这个部分被设计成一个非固定的数据结构以便在极小的空间存储尽量多的信息,它会根据对象的状态复用自己的存储空间。

类型指针:即对象指向它的类元数据的指针,JVM通过这个指针确定这个对象是哪个类的实例。另外Java数组的对象头必须有一块记录数组长度的数据。

2.实例数据

即程序代码中定义的各种类型的字段内容(从父类继承的和本类中定义的)。

HotSpot虚拟机默认的分配策略为:longs/doubles、ints、shorts/chars、bytes/booleans、oops(普通对象指针),可以看到:相同宽度的字段总被分配到一起。

3.对齐填充

这部分空间仅仅起到占位符的作用,不一定存在。

因为HotSpot VM的要求对象起始地址必须是8字节的整数倍,即对象的大小必须是8字节的整数倍。所以当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

这里分析一个问题:为什么要进行内存对齐?

因为内存对齐在一些情况下可以减少CPU读取内存的次数以及一些运算来提高性能。(空间换时间的操作)

对象的访问定位

创建了对象就是为了使用它,Java程序通过栈中的一个强引用reference来操作堆中的这个具体对象。但是Java虚拟机中只规定了一个指向对象的引用,并没有定义该引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象具体的访问方式取决于虚拟机具体的实现。

当前主流访问方式是句柄和直接指针:

句柄

JVM——Java对象的创建、内存布局和访问定位

Java堆中会划分一块内存作为句柄池,reference中存的就是对象的句柄地址,句柄中包含了对象的实例数据和类型数据各自的具体地址。

  • 优点:由于reference中存储的是稳定的句柄地址,在对象被移动时(如GC过程中的对象移动),只需改变句柄中实例数据指针,而reference本身不用动。

  • 缺点:增加了一次指针定位的时间开销。

直接指针

JVM——Java对象的创建、内存布局和访问定位

reference对象直接存储对象地址

  • 优点:速度快,节省了一次指针定位的时间开销。
  • 缺点:在对象被移动时reference本身需要被修改。

参考资料

《深入理解Java虚拟机》

JVM:这是一份全面 & 详细的 垃圾收集算法(GC) 学习指南