文章目录

  • Java对象的内存分配
  • 对象的创建流程
  • 类加载检查
  • 对象内存分配
  • 对象的内存布局
  • 对象头区域
  • 实例数据区域
  • 填充对齐区域
  • 对象的访问定位方式
  • 句柄访问
  • 直接指针访问


Java对象的内存分配

对象的创建流程

java 为对象分配空间 java对象分配内存_java

  1. 虚拟机收到new指令触发。
  2. 类加载检查:会判断类是否已经被加载,如果没有被加载则需要先执行类加载流程,对象所需内存大小在类加载完后可以完全确定。
  3. 为对象分配内存,从堆中划分出一块确定大小的内存。
  4. 内存分配完后,虚拟机需要将分配到的内存空间初始化为零(不包含对象头),保证了对象的实例字段在不赋初始值时也能直接使用。
  5. 为对象进行必要的设置:设置这个对象属于哪个类的实例、类的元数据、对象的哈希码、对象的GC分代年龄等信息,并存放在对象头中。
  6. 一个新的对象创建完毕。

类加载检查

  • 检查该指令单参数是否能在常量池中定位到一个类的符号引用;
  • 检查这个符号引用代表的类是否已经被加载、解析、初始化,如果没有,需要先执行类加载流程。

对象内存分配

java 为对象分配空间 java对象分配内存_对象的创建流程_02

堆内存是否规整:

  • 规整:已使用的内存在一边,未使用内存在另一边。
  • 不规整:已使用内存和未使用相互交错。

堆内存是否规整是由垃圾收集器是否带有压缩整理功能决定的。

内存分配会根据堆内存是否绝对规整,分两种分配方式:

  • 指针碰撞。
  • 空闲列表。

分配过程:

  • 如果堆内存绝对规整,采用指针碰撞方式。分配过程:将已使用内存和为使用内存之间放一个分界点的指针,分配内存时,指针会向未使用内存方向移动,移动一段与对象大小相等的距离。
  • 如果堆内存不绝对规整,采用空闲列表方法,分配过程:虚拟机内部维护了一个记录可用内存块的列表,在分配时从列表找一块足够大的空间划分给对象实例,并更新列表上的记录。

给对象分配内存是线程不安全的。

对象的内存布局

java 为对象分配空间 java对象分配内存_对象的内存分配_03

在Java虚拟机(HotSpot)中,对象在堆内存中的可分三块:

  • 对象头区域
  • 实例数据区域
  • 对齐填充区域

对象头区域

存储的信息包含两部分:

  • 对象自身的运行时数据(Mark Word):哈希码、GC分代年龄、锁状态标志等。
  • 对象类型指针:即对象指向它的类元数据的指针,虚拟机通过指针来确定对象属于哪个类的实例。

如果对象是数组,那么在对象头中必须还有一块用于记录数组长度的数据。

实例数据区域

真正存放对象实例数据的地方。

填充对齐区域

这部分不一定存在,没有特别含义,仅仅是占位符。这是因为HotSpot要求对象起始地址都是8字节的整数倍,如果不是则填充对齐。

对象的访问定位方式

创建对象后,Java程序通过栈上的引用类型(reference)访问堆中的对象实例。

由于引用类型数据(reference)在虚拟机中只规定了一个指向对象的引用,但没有规定采用何种方式区定位访问堆中的对象的具体位置,所以对象的访问方式具体由虚拟机实现。

目前大部分JVM虚拟机,包括HotSpot虚拟机采用的是直接指针访问。

目前对象访问方式有两种:

  • 句柄访问
  • 直接指针访问

句柄访问

java 为对象分配空间 java对象分配内存_对象的访问方式_04

在Java堆中划分出一块内存作为句柄池,虚拟机栈中reference数据中存储的是对象的句柄地址,程序通过访问栈中的reference数据获取对象的句柄地址,句柄中的每条记录包含对象实例数据的指针对象类型数据的指针,再通过句柄定位和访问对象。

优点:

  • reference存储稳定的句柄地址,对象移动(垃圾收集)时只会改变句柄中实例数据指针,reference不需要做改变。

缺点:

  • 增加了指针定位的开销

场景:

  • 适合频繁移动对象地址

直接指针访问

java 为对象分配空间 java对象分配内存_对象的访问方式_05

栈中reference数据存储的是对象地址,放在对象头中,程序通过访问栈中reference数据获取对象的地址,直接访问对象实例数据,再通过对象类型数据的指针访问对象类型数据。

优点:

  • 速度快,节省了一次指针定位的开销。

缺点:

  • 对象移动时需要重新定位。

场景:

  • 适合频繁访问对象。