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

当 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何禁止孩子在电脑中浏览某些网页?

在使用电脑的过程中&#xff0c;我们会使用浏览器来查看网页。而在孩子使用电脑的过程中&#xff0c;有些网页并不适合孩子查看。因此&#xff0c;我们需要禁止孩子浏览不健康的网页。那么&#xff0c;该如何禁止孩子在电脑中浏览某些网页呢&#xff1f; 定时关机3000简介 定时…

【docker】部署minio对象存储并用rclone同步

docker部署minio对象存储并用rclone同步 本文首发于 ❄️慕雪的寒舍 1.什么是minio&#xff1f; minio是一个开源的对象存储服务器&#xff0c;兼容S3协议。 官网&#xff1a;https://min.io/ 官方在开源的基础上也提供云端S3服务&#xff0c;分为个人和企业&#xff0c;有不…

Axure的动态图使用以及说明

认识Axure动态图 Axure动态图是Axure中的一种功能&#xff0c;它允许用户在原型中添加动画效果和交互动作&#xff0c;使原型更加生动和具有真实的用户体验。用户可以通过添加动态图来展示页面过渡、按钮点击、下拉菜单等交互操作的效果。 这是&#xff1a;就是我们今天要叫的…

ISCTF(a)

where_is_the_flag 答案应该被分成了三份了 蚁剑连接看看 第一个 第二个 第三个&#xff0c;在www下 Yunxi{0797d78c-0cb2-4cfb-87e6-f9c102f716f3} 命令执行 POST : 1 system ( tac flag.php ); 1 system ( tac /flag2 ); 1 system ( env ); 1z_Ssql 使用万能密码 后…

【数学建模】《实战数学建模:例题与讲解》第十四讲-模拟退火、遗传算法(含Matlab代码)

【数学建模】《实战数学建模&#xff1a;例题与讲解》第十四讲-模拟退火、遗传算法&#xff08;含Matlab代码&#xff09; 基本概念模拟退火&#xff08;Simulated Annealing&#xff09;遗传算法&#xff08;Genetic Algorithms&#xff09; 习题14.1&#xff08;1&#xff09…

Leetcode—118.杨辉三角【简单】

2023每日刷题&#xff08;六十&#xff09; Leetcode—118.杨辉三角 实现代码 class Solution { public:vector<vector<int>> generate(int numRows) {vector<vector<int>> ans(numRows);for(int i 0; i < numRows; i) {ans[i].resize(i 1);ans…

小狐狸GPT付费2.4.9弹窗版学习源码介绍

小狐狸GPT付费2.4.9弹窗版学习源码是一套基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;模型的开源代码库&#xff0c;旨在帮助开发者快速构建和训练自己的语言模型。该源码集成了多个先进的自然语言处理技术&#xff0c;包括预训练、微调、对话生成等&am…

BugKu-Web-滑稽

题目环境 持续的动态图片 F12审查元素 拿下flag&#xff1a;flag{595d994a34342417bfc3a3c3a23e0a48}

基于ssm新锐台球厅管理系统的设计与实现论文

新锐台球厅管理系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;作为一般的台球厅都会跟上时代的变化&#xff0c;用上计算机来代表重复性的劳动&#xff0c;并且给用户一种新奇的感受&#xff0c;实现新锐台球厅管理系统 在技术上已成熟。本文介绍了新锐台…

【linux】(ubuntu)下 QT 出现的问题

错误一&#xff1a;Make 运行QT程序以后出现这样的错误。 【解决方法】 我的ubuntu版本是18.04.4&#xff0c; 原因1&#xff1a;没有更换软件源 原因2&#xff1a;没安装相关 软件包 注意&#xff1a;这一步很有可能卡死这一步&#xff0c;所以如果一直卡在这并且进度…

51单片机的串口通信

串口通信 本文主要涉及51单片机的串口以及串口通信&#xff0c;包括串口控制寄存器的设置以及波特率的计算方法等。 文章目录 串口通信一、 串行通信与并行通信二、 单工、半双工与全双工通信三、 单片机串口介绍&#xff08;1&#xff09;串口控制寄存器 SCON&#xff08;2&am…

C++:命名空间

从今天正式开始对C的学习&#xff0c;这里只学习C对C的拓展&#xff0c;和C相同的部分在C语言专栏中都可以找到&#xff0c;我们先看一段C代码 #include<iostream> using namespace std; int main() {cout<<"hello world<<endl;return 0; } 同样也是打…

Docker--Docker镜像仓库

一、搭建私有镜像仓库 搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现。 官网地址&#xff1a;https://hub.docker.com/_/registry &#xff08;一&#xff09;简化版镜像仓库 Docker官方的Docker Registry是一个基础版本的Docker镜像仓库&#xff0c;具备仓库…

pycharm某个xxx.sh文件显示问号,无法编辑

文章目录 pycharm某个xxx.sh文件显示问号,无法编辑其他参考 pycharm某个xxx.sh文件显示问号,无法编辑 问题描述&#xff1a;pycharm某个xxx.sh文件显示问号,无法编辑 问题分析&#xff1a; pycharm无法识别文件类型。 问题解决&#xff1a; 在pycharm中选中该文件&#xff0…

自动化测试(三)webdriver的常用api(1)

目录 等待 sleep休眠 隐式等待 显式等待 打印信息 打印title 打印url 浏览器的操作 浏览器最大化 设置浏览器宽、高 操作浏览器的前进、后退 控制浏览器滚动条 键盘事件 键盘按键用法 键盘组合键用法 鼠标事件 定位一组元素 前面两章我们讲了selenium环境的…

Eolink Apikit 如何进行 Websocket 接口测试?

什么是 websocket &#xff1f; WebSocket 是 HTML5 下一种新的协议&#xff08;websocket协议本质上是一个基于 tcp 的协议&#xff09;。 它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和带宽并达到实时通讯的目的 Websocket 是一个持久化的协议。…

『K8S 入门』二:深入 Pod

『K8S 入门』二&#xff1a;深入 Pod 一、基础命令 获取所有 Pod kubectl get pods2. 获取 deploy kubectl get deploy3. 删除 deploy&#xff0c;这时候相应的 pod 就没了 kubectl delete deploy nginx4. 虽然删掉了 Pod&#xff0c;但是这是时候还有 service&#xff0c…

图片的批量建码怎么做?一图一码的制作方法

在使用图片展示内容时&#xff0c;经常会有同一类型的图片信息是有区别的&#xff0c;如果需要将每张图片批量生成二维码图片&#xff0c;那么出了一张一张去制作之外&#xff0c;有没有能够一键批量建码的功能可以解决这个问题呢&#xff1f;下面来给大家分享一下图片批量建码…

2024 年值得收藏的 10 款 iPhone 数据恢复软件评论

iPhone 让您将数字生活装在口袋里。从您所爱之人的照片和视频&#xff0c;到与学校和工作相关的文档&#xff0c;再到重要的备忘录和日历约会&#xff0c;iPhone 内的微型存储芯片可以容纳的数据量是惊人的。 唯一的问题是&#xff0c;很快就会丢失这些数据。一次错误的点击或…

FLStudio2024版本新增功能及21.3版本安装下载教程

FLStudio21.0.2.3中文版完整下载是最好的音乐开发和制作软件也称为水果循环。它是最受欢迎的工作室&#xff0c;因为它包含了一个主要的听觉工作场所。最新FL有不同的功能&#xff0c;如它包含图形和音乐音序器&#xff0c;帮助您使完美的配乐在一个美妙的方式。此程序可用于Mi…