【Java JVM】实例对象内存布局

news2024/9/25 7:30:29

当 Java 应用启动后, 基本就是在不断的创建对象, 回收对象的过程中。
而这些创建的对象基本都是存放在应用的堆 (heap) 中, 但是这些对象在堆中又是什么样子的呢?
在这篇文章中, 我们分析一下 Java JVM 中实例对象的内存布局。

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

大体的样子如下:
Alt 'JVM 实例的内存布局'

1 对象头 (Object Header)

Java 实例的对象头主要包含 2/3 个部分, 如果是对象的话, 只包含 2 部分 Mark Word 和 Klass Pointer, 如果是数组的话, 还会多一个 Array Length。

Mark Word
用于存储对象自身的运行时数据, 如哈希码 (HashCode), GC 分代年龄, 锁状态标志, 线程持有的锁, 偏向线程 ID, 偏向时间戳等。
这一部分在 32 位系统里面的大小为 4 个字节, 而 64 位系统里面则为 8 个字节。

Klass Pointer
类型指针, 即对象指向它的类型元数据的指针, Java 虚拟机通过这个指针来确定该对象是哪个类的实例 (并不是所有的虚拟机实现都必须在对象数据上保留类型指针, 即查找对象的元数据信息并不一定要经过对象本身)。
这一部分在 32 位系统里面的大小为 4 个字节, 而 64 位系统里面则为 8 个字节。

Array Length
当我们的对象实例是数组对象的话, 对象头里面还会有一个用于记录数组长度的数据, 大小为 4 个字节, 主要用于确定对象的大小。因为普通的 Java 对象可以通过
元数据 (即类中的属性, int 32 位, long 32 位, 所以通过属性基本可以确定一个类实例的大小) 推算出对象的大小, 但是数组的长度不确定时, 无法推算出数组的大小。

在 32 位系统中, HotSpot 里面的 Mark Work 正常情况 (对象没有被加锁, 即没有被 synchronized 加锁) 的分布如下:

Alt '32 位系统无锁状态 MarkWord 的结构'

64 位系统的话, 如图:
Alt '64 位系统无锁状态 MarkWord 的结构'

注: Mark Work 的内容不是一成不变的, 如果对象被当做 synchronized 锁的话, 其内部的内容会随锁的状态变更。

对象头一般情况下的大小:

32 位系统下: Class Pointer 4 个字节, MarkWord 4 个字节, 对象头为 8 个字节, 如果是数组的话, 再加上 4 个字节的数组长度。
64 位系统下: Class Pointer 8 个字节, MarkWord 8 个字节, 对象头为 16 个字节, 如果是数组的话, 再加上 4 个字节的数组长度。

Java 中还有一项技术会影响到对象头的大小: 指针压缩技术, 看后面的介绍。

2 实例数据 (Instance Data)

对象真正有效的信息, 也就是我们类中声明的各个字段 (包括从父类继承下来的), 每个字段都有自己的大小限制。

字段类型内存大小(单位: 字节)
boolean1
byte1
short2
char2
int4
float4
long8
double8
reference(引用类型)4 (32 位系统), 8 (64 位系统)

通过上面的大小的字段类型的, 基本可以确定每个对象的实际数据大小 (静态属性维护在类 (也就是具体的 Class 上)上, 所以不算在对象大小里面)。

每个实例的属性在内存的存储顺序会受到虚拟机的分配策略影响 (-XX:FieldsAllocationStyle) 和字段在 Java 源码中定义的顺序的影响。
HotSpot 虚拟机默认的分配顺序为 long/double, int/float, short/char, byte/boolean, reference。
在满足这个前提条件下, 父类中定义的变量会在子类的前面。
如果 HotSpot 虚拟机的 +XX:CompactFields 参数值为 true (默认为 true), 那子类之中较窄的变量也允许插入父类变量的空隙之中, 以节省出一点点空间。

举个列子, 当前父类有 2 个属性 long 和 int, 子类有 3 个属性 int, short, short。
如果按照上面的规则 4 个属性在内存的分配顺序为 long (8 个字节) int (4 个字节) int (4 个字节) short (2 个字节) short (2 个字节)。
+XX:CompactFields 设置为 true 后, 分配的顺序可能变为 long (8 个字节) int (4 个字节) short (2 个字节) short (2 个字节) int (4 个字节)。
子类 2 个 short 属性插入到父类的变量空隙中了。

3 对齐填充 (Padding)

这个不是必须, 也没有具体的含义, 只是单纯的起占位作用。他的出现与否取决于当前对象实例的内存大小。
所有的 Java 对象所占用的字节数必须是 8 的倍数。比如 一个对象的对象头的大小为 12 byte, 实例数据为 13 byte, 当前对象所占的大小为 25 byte。
但是 JVM 要求每个对象的大小必须是 8 的倍数, 这时候 padding 就其作用了, 填充 7 个字节, 凑够 32, 达到 8 的倍数。
而当对象头和实例数据刚好达到 8 的倍数, 这时候就不需要 padding 了。

之所以强制为对象大小为 8 的倍数是为了内存访问的效率, 数据对齐对处理器的访问是最佳的。

4 指针压缩 (CompressedOops)

在了解指针压缩之前, 先了解一点别的。
我们在买电脑的时候, 很多时候都会说多少位系统, 内存是多少的, 比如 64 位系统 16g 内存, 32 位系统 4g 内存。
那么是否存在 32 位系统 16g 内存, 64 位系统 64g 的内存呢?

这里面涉及一点计算机的知识。32 位系统最大支持的内存为 4g, 64 位系统最大支持的则为 1T。能支持的内存的大小取决于 CPU 的寻址能力。

CPU 的寻址能力以字节为单位。

  1. 内存把 8 个比特 (8 bit) 排成一组, 每一组为一个单位, 记为一个字节 (Byte), CPU 每次只能访问去访问一个字节 (Byte), 不能去访问每一个比特
  2. 计算机系统会给内存中的每一个字节分配一个内存地址, CPU 只要知道某个数据类型的地址, 就可以到地址所指向的内存去读取数据
  3. 在 32 位系统中, 内存地址就是 32 位的二进制数, 既从 0x00000000 到 0xFFFFFFFF, 即一共有 2^32 个地址, 每个地址对应一个字节
  4. 32 位系统的 2^32 个地址, 对应了 2^32 个字节, 也就是 4GB 的内存 (如果给 32 位系统配上了 8G 内存, 操作系统最多只能给其中的 4GB 分配地址, 其他 4GB 是没有地址的)

而我们常说的指针, 在程序中内存地址映射, 可以理解一个指针就对应了一个内存地址。通过指针就能定位到内存对应的某个位置。
在 32 位系统, 指针的大小只要 4 个字节就能包含所有的地址了, 同样的 64 位系统, 指针的大小只要 8 个字节就够了。

OK, 聊完了。下面就开始真正的指针压缩的分析!

在 JVM 中, 32 位系统的对象引用 (指针) 占 4 个字节 (4 个字节已经能够囊括所有的内存地址), 而 64 位系统的对象引用占 8 个字节。
也就是说, 64 位的对象引用大小是 32 位的 2 倍。
64 位 JVM 在支持更大堆内存的同时, 由于对象引用指针的变大却带来了其他的性能问题, 如对象占用的内存变大, 降低 CPU 缓存命中率等。

为了能够利用 64 位系统的大内存的前提, 又能使用到 32 位系统的的小内存引用指针, 就有了压缩指针 (CompressedOops) 技术 (在 64 位的机器上使用 32 位的引用的同时, 使用超过 4g 的内存)。

要达到这个的前提:
Java 中实例对象的内存大小都是 8 的倍数, 一个数是 8 的倍数的话, 那么其二进制的表示的后面三位必定是 3 个 000 (8->1000, 16->10000)。

JVM 知道每个 Java 对象的大小都是 8 的倍数, 正常存储的话, 每个的指针最后的 3 为必定都是 0, 那么这最后的 3 位完全没必要存了。
32 位的引用空出来的 3 位, 完全可以用来存储多 3 位数据, 既将一个 35 位的指针用 32 位的形式存储在 JVM 中。

Alt '35 位指针达到 32 位指针的效果'

落实到实现中就是, 将引用的数字向左移动 3 位, 得到真正的内存地址, 通过这个转换就能实现指针和真正的内存进行关联。
比如我们堆中某个变量的指针为 0, 那么都内存中的 0 (00000000 << 3, 还是 0) 的位置就能找到这个对象的实际内容。
变量的指针为 1, 那么到内存 8 (00000001 << 3, 00001000, 十进制 8) 的位置查找就行了。

说到底, 压缩指针的前提就是存储的内容本身是一个指针引用, 那么在 Java 实例中, 哪些数据是指针引用呢

  1. the klass field of every object (每个普通对象对象头里面的 Klass Pointer)
  2. every oop instance field (每个普通对象的属性)
  3. every element of an oop array (objArray) (每个普通对象数组里面的每个元素)

5 参考

《深入理解Java虚拟机》- 周志明
为什么32位系统最大只支持4G内存
CompressedOops
Compressed OOPs in the JVM

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1315049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

240Wqps,美团用户中台, 如何使用DDD架构?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 谈谈你的DDD落地经验&#xff1f; 谈谈你对DDD的理解&#x…

Canal实时同步MySQL数据到ES

一、canal简介 canal主要用途是对MySQL数据库增量日志进行解析&#xff0c;提供增量数据的订阅和消费&#xff0c;简单说就是可以对MySQL的增量数据进行实时同步&#xff0c;支持同步到MySQL、Elasticsearch、HBase等数据存储中去。 早期阿里巴巴因为杭州和美国双机房部署&…

LabVIEW实时建模检测癌细胞的异常

LabVIEW实时建模检测癌细胞的异常 癌症是全球健康的主要挑战之一&#xff0c;每年导致许多人死亡。世界卫生组织指出&#xff0c;不健康的生活方式和日益严重的环境污染是癌症发生的主要原因之一。癌症的发生通常与基因突变有关&#xff0c;这些突变导致细胞失去正常的增长和分…

深度探索Linux操作系统 —— 构建根文件系统

系列文章目录 深度探索Linux操作系统 —— 编译过程分析 深度探索Linux操作系统 —— 构建工具链 深度探索Linux操作系统 —— 构建内核 深度探索Linux操作系统 —— 构建initramfs 深度探索Linux操作系统 —— 从内核空间到用户空间 深度探索Linux操作系统 —— 构建根文件系统…

漏刻有时数据可视化Echarts组件开发(42)动态创建DIV容器

效果展示 引入外部文件 <script src"js/jquery.min.js"></script><script type"text/javascript" src"js/echarts.5.4.3.min.js"></script>CSS层叠样式表 实现一行3列效果&#xff0c;自动换行&#xff1b; .ecbox {he…

卷积神经网络(CNN)中感受野的计算问题

感受野 在卷积神经网络中&#xff0c;感受野&#xff08;Receptive Field&#xff09;的定义是卷积神经网络每一层输出的特征图&#xff08;feature map&#xff09;上每个像素点在原始图像上映射的区域大小&#xff0c;这里的原始图像是指网络的输入图像&#xff0c;是经过预处…

在开发微信小程序的时候,报错navigateBack:fail cannot navigate back at firstpage

这个错误的意思是&#xff1a;在这个页面已经是第一个页面了&#xff0c;没办法再返回了 报错原因 这个错误原因其实也简单&#xff0c;就是在跳转的时候使用了wx.redirectTo()&#xff0c;使用wx.redirectTo()相当于重定向&#xff0c;不算是从上一个页面跳转过来的&#xf…

C# WPF上位机开发(权限管理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 如果软件本身是一个人使用&#xff0c;那么基本上不存在权限管理的问题。但是如果软件不是一个人&#xff0c;而是多个人&#xff0c;甚至是不同班…

python进度条

分享一个进度条python库 瞬间觉得很酷 :)) 它的名字叫tqdm 效果图&#xff1a; 代码&#xff1a; import time from tqdm import tqdmfor i in tqdm(range(100), desc"Loading", unit"kb"):time.sleep(0.1)

iPhone 与三星手机:哪一款最好?

三星比苹果好吗&#xff1f;还是苹果比三星更好&#xff1f; 小米公司如何称霸全球智能手机市场&#xff1f;小米公司&#xff0c;由雷军创立于2010年&#xff0c;是一家领先的电子巨头。以其MIUI系统和互联网服务闻名&#xff0c;小米公司在全球智能手机市场中稳居前列。小米…

【zetoro】文献管理工具使用

文章目录 一、zetoro文献管理二、论文中插入文献三、插件推荐&#xff1a; 一、zetoro文献管理 ➡️如何下载&#xff1a;搜索zotero即可找到官网直接下载安装 ➡️如何导入文献&#xff1a; 1本地文献拖拽导入 2各文献搜索平台上下载zotero格式文件&#xff0c;在zotero-文件…

提升英语学习效率,尽在Eudic欧路词典 for Mac

Eudic欧路词典 for Mac是一款专为英语学习者打造的强大工具。无论您是初学者还是高级学习者&#xff0c;这款词典都能满足您的需求。 首先&#xff0c;Eudic欧路词典 for Mac具备丰富的词库&#xff0c;涵盖了各个领域的单词和释义。您可以轻松查询并学习单词的意思、用法和例…

spring boot 实现直播聊天室

spring boot 实现直播聊天室 技术方案: spring bootwebsocketrabbitmq 使用 rabbitmq 提高系统吞吐量 引入依赖 <dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.42&…

《人工智能导论》知识思维导图梳理【第6章节】

文章目录 第六章 知识图谱1 知识图谱概述2 知识图谱相关概念3 知识图谱的逻辑结构4 知识图谱的数据存储5 知识图谱的构建过程6 例题 markdown内容的分享 第六章 知识图谱 1 知识图谱概述 2 知识图谱相关概念 3 知识图谱的逻辑结构 4 知识图谱的数据存储 5 知识图谱的构建过程 6…

fl studio20中文内测版下载2024最新完美实现汉化

fl studio20是一款众所周知的水果编曲软件&#xff0c;能够剪辑、混音、录音&#xff0c;它的矢量界面能更好用在4K、5K甚至8K显示器上&#xff0c;还可以可以编曲、剪辑、录音、混音&#xff0c;让你的计算机成为全功能录音室&#xff0c;不论是在功能上面还是用户界面上都是数…

小程序使用Nodejs作为服务端,Nodejs与与MYSQL数据库相连

小程序使用Nodejs作为服务端,Nodejs与MYSQL数据库相连 一、搭建环境二、配置Nodejs三、与小程序交互四、跨域处理/报错处理五、nodejs连接mysql数据库六、微信小程序连接nodejs报错七、小程序成功与服务端相连,且能操作数据库一、搭建环境 新建空文件夹:Win + R进入cmd命令界…

Composer 安装与使用

Composer 是 PHP 的一个依赖管理工具。我们可以在项目中声明所依赖的外部工具库&#xff0c;Composer 会帮你安装这些依赖的库文件&#xff0c;有了它&#xff0c;我们就可以很轻松的使用一个命令将其他人的优秀代码引用到我们的项目中来。 Composer 默认情况下不是全局安装&a…

为uniDBGrid设置文字操作栏

为uniDBGrid设置文字操作栏&#xff0c;如下图的效果&#xff0c;用户点击审核&#xff0c;执行审核代码&#xff0c;点退回&#xff0c;执行退回代码&#xff1a; 对于Web应用界面&#xff0c;这是最常见的方式&#xff0c;那对于我等Delphi开发者来说&#xff0c;基于uniGUI该…

RT-DETR优化:轻量化卷积设计 | DualConv双卷积魔改RT-DETR结构

🚀🚀🚀本文改进: DualConv双卷积魔改v8结构,达到轻量化的同时并能够实现小幅涨点 🚀🚀🚀RT-DETR改进创新专栏:http://t.csdnimg.cn/vuQTz 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; RT-DETR模型创新优化,涨点技巧分享,科研小助手; 1.DualC…

SpringBoot 源码解析1:环境搭建

SpringBoot 源码解析1&#xff1a;环境搭建 1.项目结构2.pom.xml3.MyApplication 1.项目结构 最简单的spring-web项目 2.pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns…