Java对象刨根问底

如何创建一个对象

Java创建一个对象,最简单的方式就是

1
Object a = new Object();

可以,大家有没有想过,在JVM中,创建一个对象的流程是如何呢?其实具体流程入下图所示。

img

###分配内存

对象所需的内存大小,在类加载完成后便可以完全确认,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。那么如何划分,有两种方式:

  • 指针碰撞(Bump The Pointer):假设Java堆中的内存是绝对规整的,所有用过的内存都放到一边,空闲的内存放到另一边,中间放着一个指针作为分界点的指示器,那所分配的内存就是朝着空闲的那一边“挪动”与对象大小一样的距离。
  • 空闲列表(Free List):如果Java堆中的内存不规整,已使用的内存和未使用的内存相互交错,就没办法简单进行指针碰撞了,需要维护一个列表,记录哪些内存块是可用的,在分配的时候,找一块足够大的空间分配给对象,并更新列表的记录。

因此,在使用Serial,ParNew等带Compact过程的收集器时,系统采用的分配方式是指针碰撞法,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用的是空闲列表法。

除了分配方法外,还需要考虑同步的问题。实际上虚拟机采用了CAS配上失败重试的方式保证更新的原子性,另一种是把内存分配的动作按照线程划分在不同的空间之中,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB),可以通过-XX:+UseTLAB参数来设定。

对象内存布局

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

对象头

HotSpot虚拟机对象头包括两部分内容:

  • 第一部分

用于存储对象自身的运行数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,这部分长度在32bit和64bit虚拟机中分别为32bit和64bit,官方称它为“Mark Word”。

存储内容 标志位 状态
对象哈希码、对象分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 膨胀(重量级锁定)
空,不需要记录信息 11 GC标记
偏向线程ID,偏向时间戳,对象分代年龄 01 可偏向
  • 第二部分

类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

对象访问定位

有两种方式,句柄访问直接指针访问。这两种访问方式各有优点,使用句柄的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾回收移动对象是很常见的事情)时只会改变句柄中的实例数据指针,而reference本身不需要更新。使用直接指针访问方式最大好处就是速度更快,它节省一次指针定位的时间开销,由于对象访问在Java中非常频繁,这类开销积少成多,将是一个比较可观的提升。HotSpot就是采用直接指针法。

img

​ 通过句柄访问对象

img

​ 通过直接指针访问对象

# java

评论