HotSpot虚拟机中对象的分配、布局和访问

本篇文章以HotSpot虚拟机和常用的内存区域Java堆为例,深入探讨HotSpot在Java堆中对象分配、布局和访问的全过程。

对象的创建

在Java对象的创建和初始化一文中,我们知道了创建对象的几种方式。虚拟机在遇到创建指令时,首先会去检查对象所代表的类是否已经被加载、解析和初始化过。如果没有,那么必须执行相应的类加载过程,详细细节可参考Java类的加载和初始化。

空间分配
在类加载完成后,将为对象分配空间(把一块确定大小的内存从堆中划分出来)。分配方式包括以下两种:
1、假设Java堆中内存是绝对规律的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,分配内存就仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为指针碰撞


2、如果堆内存不是规律的,已使用的内存和空闲内存相互交错,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为空闲列表

选择哪种分配方式由Java堆是否规整决定,而堆是否规整又是由所采用的垃圾收集器是否带有压缩整理功能决定。因为,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。

在分配空间时,还需考虑并发问题。可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案:
1、对分配空间的动作进行同步处理,实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。
2、把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。然后对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。

对象的内存布局

在HotSpot虚拟机中,对象在内存中存储的布局分为3块区域:对象头实例数据对象填充


HotSpot对象头Mark Word

对象的访问

建立对象是为了使用对象,Java程序需要通过栈上的reference数据来操作堆上的具体对象。reference类型是一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种:
1、使用句柄访问,Java堆中会划分出一块内存来作为句柄池,reference存储对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。



2、使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息(对象头中的类型指针),而reference中存储的直接就是对象地址。


这两种对象访问方式各有优劣,使用句柄的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只需改变句柄中的实例数据指针,而reference本身不需要修改。

使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销。就HotSpot虚拟机而言使用的是直接指针方式。

你可能感兴趣的:(HotSpot虚拟机中对象的分配、布局和访问)