【学习笔记】Java虚拟机(三)HotSpot虚拟机探秘

HotSpot虚拟机探秘


1、对象的创建


① 虚拟机遇到new指令时,首先检查这个指令参数是否能在常量池中定位到一个类的符号引用
,并检查其是否已被加载、解析和初始化过,如果没有则先进行类的加载。


② 完成后虚拟机将为它分配内存,内存大小在类加载完后已经确定。如果内存是绝对规整的
,那么分配内存的过程就是将分界点的指示器向空闲空间那边挪动相应的内存大小即可,这种
方式称为“指针碰撞”(Bump the Pointer)。如果内存不规整,那么虚拟机就要找到一块足够
大的空间分配给对象实例并更新自己所维护的内存列表,这种方式叫做“空闲列表”(Free
List)。堆是否规整由垃圾收集器是否带有压缩整理功能决定,因此使用Serial、ParNew等带
有Compact过程的收集器时,系统采用指针碰撞,而CMS这种基于Mark-Sweep算法的收集
器会使系统使用空闲列表方式。


③ 在并发的情况下线程不是安全的,可能会出现给A对象分配内存时,指针还没来得及修改,
对象B又同时使用了原来的指针分配内存。这种问题有两种解决方案:一是对分配内存空间的
动作进行同步处理,虚拟机采用CAS配上失败重试的方式确保更新操作的原子性;二是把内存
分配动作按照线程划分,每个线程在堆中预先分配一小块内存,成为本地线程分配缓冲
(Thread Local Allocation Buffer,TLAB)。只有在TLAB用完并分配新的TLAB时才需要同
步锁定,可以通过-XX:+/-UseTLAB参数来设定是否启用TLAB。

④ 内存分配完成后,虚拟机将分配到的内存空间都初始化为零值(除了对象头),如果使用
TLAB则可在TLAB过程中完成。这一步操作保证了对象实例字段在Java代码中可以不赋值就访
问到该字段数据类型所对应的零值。


⑤ 接下来虚拟机要设置对象的头信息(Object Header),例如对象的父类是谁、如何能找到
类的元数据信息、对象的哈希码、对象的GC分代年龄等。根据虚拟机的运行状态,如是否启
用偏向锁等,对象头信息的设置方式会有不同。


⑥ 一般来说(由字节码中是否跟随invokespecial指令所决定)执行new指令后就会执行
方法,按照程序员的需要初始化对象的实例。

2、对象的内存布局


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


对象头包含两部分信息,分别保存运行时数据和类型指针:


① 运行时数据存储了哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有锁、偏向
线程ID、偏向时间戳等。在32位和64位虚拟机中长度分别为32bit和64bit,官方称为“Mark
Word”。Mark Word是一个非固定的数据结构,会根据对象的状态复用自己的存储空间。借图:
【学习笔记】Java虚拟机(三)HotSpot虚拟机探秘
文章图片




② 类型指针即对象指向它的类元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的
实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,有些情况查找对象元数据
信息并不一定经过对象本身。另外,如果对象是一个Java数组,对象头中还会记录数据的长度
,因为虚拟机无法从数组的元数据中确定它的大小。


实例数据存储的是在代码中定义的各种类型的字段内容,不伦是继承的还是子类本身的。这部分存储
顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义的顺序影响,
HotSpot默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops
(Ordinary Object Pointers)。相同宽度的字段被分配到一起,在满足这个规则的前提下父类定义
的变量会出现在子类之前。如果CompactFields参数为true(默认为true),子类中较窄的变量也可
能插入到父类变量的空隙中。


对齐填充不是必然存在的,也没有特别的意义。因为HotSpot VM的自动内存管理系统要求对象的大
小必须是8字节的整数倍,对象头部分已经满足了这个规则,所以当对象实例数据部分没有对齐时,
用对齐填充来补全。


3、对象的访问定位


我们通过栈上的reference数据来操作堆上的具体对象,主流的访问方式有句柄和直接指针两种:


① 使用句柄访问时Java堆会划出一块内存作为句柄池,reference存储的就是对象的句柄地址
,句柄地址中包含了对象实例数据与类型数据各自的具体地址。借图:
【学习笔记】Java虚拟机(三)HotSpot虚拟机探秘
文章图片



② 使用直接指针访问时,Java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,
reference中存储的就是对象地址。借图:
【学习笔记】Java虚拟机(三)HotSpot虚拟机探秘
文章图片



使用句柄访问的最大好处是reference中存储的就是稳定的句柄地址,在对象被移动时只改变句柄中
的实例数据指针,而不修改reference本身。使用直接指针的好处是速度更快,节省了一次指针定位
的时间开销,HotSpot使用的就是这种方式。
【【学习笔记】Java虚拟机(三)HotSpot虚拟机探秘】

    推荐阅读