一文带你探秘HotSpot虚拟机对象

引入

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

1. 对象的内存布局

HotSpot虚拟机里,对象在 内存中的存储布局可以划分为三个部分:对象头Header)、实例数据Instance Data)和对齐填充Padding)。

2. 对象的创建

2.1 对象的创建流程

JVM遇到一条字节码new指令时:

  • 检查指令的参数是否能在常量池中定位到一个类的符号引用;
  • 检查这个符号引用代表的类是否已经被加载、解析和初始化。没有则必须执行相应的类加载过程;
  • 类加载检查通过后,JVM为新生对象分配内存
  • 内存分配后,JVM将分配到的内存空间(但不包括对象头)都初始化为零值;
  • 设置对象头(Object Header),如:这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。

虚拟机 的视角来看,完成上述流程后一个新的对象已经产生了。但是从Java程序 的视角看来,对象创建才刚刚开始------构造函数 ,即Class文件中的<init>(),所有的字段都为默认的零值,对象需要的其他资源和状态信息也还没有按照预定的意图构造好。

new指令之后会接着执行<init> ()方法,按照程序员的意愿对对象进行初始化 ,这样一个真正可用的对象才算完全被构造出来。

2.2 内存分配

对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务实际上便等同于把一块确定大小的内存块从Java堆中划分出来

2.2.1 指针碰撞(Bump The Pointer

假设Java堆中内存是绝对规整 的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针 作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为指针碰撞Bump The Pointer)。

2.2.2 空闲列表(Free List

假设Java堆中的内存并不是规整 的,已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表 ,记录上哪些内存块可用 的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为空闲列表Free List

选择哪种分配方式由Java堆是否规整 决定,而Java堆是否规整又由所采用的垃圾收集器 是否带有空间压缩整理Compact)的能力决定。

2.3 堆抢占的情况下,JVM如何保证线程安全

对象创建在JVM中发生很频繁,在并发情况下也并非线程安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。

JVM提供了两种方案来来解决上述问题:

  • CAS: 对分配内存空间的动作进行同步 处理,实际上虚拟机是采用 CAS配上失败重试 的方式保证更新操作的原子性;

  • TLAB: 是把内存分配的动作按照线程划分在不同的空间 之中进行,即每个线程Java堆中预先分配一小块内存,称为本地线程分配缓冲Thread Local Allocation BufferTLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。

虚拟机是否使用 TLAB,可以通过-XX:+/-UseTLAB参数来设定。

3. 对象的访问定位

创建好了对象自然是为了后续使用这个对象,而对象的访问方式实际上由JVM实现而决定,主流的访问方式主要有两种:

  • 句柄
  • 直接指针

句柄Handle):用于标识某个资源或对象的唯一标识符 。本质上是一个整数值或指针类型,它与资源实体之间的映射关系由操作系统维护。句柄只能通过系统函数来访问和操作。

3.1 使用句柄访问

3.1.1 访问方式

Java堆中划分出一块内存作为句柄池reference中存储的是对象的句柄地址 ,句柄中包含对象实例数据 以及类型数据各自具体的地址信息

3.1.2 优点

reference中存储的是稳定句柄地址 ,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改

3.2 使用直接指针访问

3.2.1 访问方式

Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址 ,如果只是访问对象本身的话,就不需要多一次间接访问的开销。

HotSpot采用这种方式进行对象访问。

3.2.2 优点

速度更快 ,它节省了一次指针定位的时间开销,由于对象访问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本。

参考资料

相关推荐
金灰1 分钟前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5
菜鸟一皓2 分钟前
IDEA的lombok插件不生效了?!!
java·ide·intellij-idea
爱上语文5 分钟前
Java LeetCode每日一题
java·开发语言·leetcode
bug菌28 分钟前
Java GUI编程进阶:多线程与并发处理的实战指南
java·后端·java ee
程序猿小D41 分钟前
第二百六十九节 JPA教程 - JPA查询OrderBy两个属性示例
java·开发语言·数据库·windows·jpa
Ray Wang1 小时前
3.JVM
jvm
极客先躯2 小时前
高级java每日一道面试题-2024年10月3日-分布式篇-分布式系统中的容错策略都有哪些?
java·分布式·版本控制·共识算法·超时重试·心跳检测·容错策略
夜月行者2 小时前
如何使用ssm实现基于SSM的宠物服务平台的设计与实现+vue
java·后端·ssm
程序猿小D2 小时前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa
Yvemil72 小时前
RabbitMQ 入门到精通指南
开发语言·后端·ruby