【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

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

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

相关文章

结构体基础全家桶(2)结构体指针

目录 指向结构体类型数据的指针&#xff1a; 指向结构体变量的指针&#xff1a; 创建&#xff1a; 应用&#xff1a; 注意事项&#xff1a; 指向结构体数组的指针 创建&#xff1a; 应用&#xff1a; 注意&#xff1a; 用结构体变量和指向结构体的指针做函数的参数 …

eNSP小实验---(简单混合)

实验目的&#xff1a;实现vlan10 vlan20 172网段用户互访 1.拓扑图 2.配置 PC1 其它同理 SW4 <Huawei> <Huawei>u t m Info: Current terminal monitor is off. <Huawei>sys <Huawei>sys Enter system view, return user view with CtrlZ. [Hua…

深度学习小白学习路线规划

作为深度学习的初学者&#xff0c;以下是一个建议的学习路线&#xff0c;可以帮助你逐步掌握图像分类、目标检测与跟踪、实例分割和姿态估计&#xff1a; 掌握这些&#xff0c;计算机视觉算是入门了&#xff01; 1. 基础知识&#xff1a; 学习Python编程语言&#xff0c;它是…

当在VMware Workstation Pro 中查询不到ens33网卡的IP

在学习中我们经常要去查询ens33的IP&#xff0c;但是有时候会查询不到&#xff0c;今天就遇到了这样的问题并且找到了解决方法 记录一下 ip a 查询不到IP 显示代码为 ens33: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether …

[论文笔记] GAMMA: A Graph Pattern Mining Framework for Large Graphs on GPU

GAMMA: A Graph Pattern Mining Framework for Large Graphs on GPU GAMMA: 基于 GPU 的针对大型图的图模式挖掘框架 [Paper] [Code] ICDE’23 摘要 提出了一个基于 GPU 的核外(out-of-core) 图模式挖掘框架(Graph Pattern Mining, GPM) GAMMA, 充分利用主机内存来处理大型图…

〖大前端 - 基础入门三大核心之JS篇(55)〗- 内置对象

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;哈哥撩编程&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xff0c;目前在公司…

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

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

QML 自定义进度条组件开发

一、效果预览 二、介绍&#xff1a; 自定义的QML 屏幕亮度拖动进度条组件CusProgressBar 可跟鼠标移动 更改进度条样式 三、代码 import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Controls.Material 2.12/***author:Zwj*csdn:来份煎蛋吧*date:2023/12/16*…

如何禁止服务器自动休眠

如何禁止服务器自动休眠 有时候服务器自己休眠&#xff0c;导致系统web站点无法访问&#xff0c;下面是解决办法&#xff01; 禁止服务器自动进入休眠状态的具体方法可能会因使用的Linux发行版而有所不同。以下是一些通用的方法&#xff0c;你可以根据你的系统选择适用的&#…

【机器学习】044_Kaggle房价预测(机器学习模型实战)

参考自《动手学深度学习》中“Kaggle比赛实战&#xff1a;预测房价”一节 一、数据准备 首先从网站上下载要获取的房价数据。 DATA_HUB是一个字典&#xff0c;用来将数据集名称的字符串和数据集相关的二元组一一对应。 二元组包含两个值&#xff1a;数据集的URL和用来验证文…

python实现最小二叉堆---最小堆结构

#来源于MOOC学习以及数据结构与算法分析# 在我们学习最小二叉堆代码实现之前&#xff0c;我们需要去了解一下&#xff0c;什么是最小二叉堆&#xff08;也有最大二叉堆&#xff0c;也叫最大堆&#xff09;。 也就是说什么是二叉堆&#xff1f;&#xff1f;&#xff1f;&#…

HiveSql语法优化三 :join优化

前面提到过&#xff1a;Hive拥有多种join算法&#xff0c;包括Common Join&#xff0c;Map Join&#xff0c;Bucket Map Join&#xff0c;Sort Merge Buckt Map Join等&#xff1b;每种join算法都有对应的优化方案。 Map Join 在优化阶段&#xff0c;如果能将Common Join优化为…

Linux 中的网站服务管理

目录 1.安装服务 2.启动服务 3.停止服务 4.重启服务 5.开机自启 6.案例 1.安装服务 网址服务程序 yum insatll httpd -y 查看所有服务 systemctl list-unit-files 2.启动服务 systemctl start httpd 查看服务进程&#xff0c;确认是否启动 ps -ef|grep httpd 3.停止…

python分析数据出现Text input context does not respond to _valueForTIProperty错误

一开始运行脚本还是不报错的&#xff0c;脚本内容部分如下&#xff1a; 出现了如下的效果图&#xff1a; 后面隔了几天再次运行居然报错了&#xff0c;如下图所示&#xff0c;但是也没有更改代码啊。后来发现原来是输入法导致的&#xff0c;把输入法切换成英文状态就不报错啦。…

linux下sys目录与proc目录的作用

sys目录作用 在Linux系统中&#xff0c;/sys目录是一个特殊的虚拟文件系统&#xff08;sysfs&#xff09;&#xff0c;用于提供对内核和设备的运行时信息的访问。它是在内核中运行的驱动程序和子系统的接口&#xff0c;可以用于获取和配置系统的硬件和内核信息。 以下是/sys目…

条件分布律

设是二维离散型随机变量&#xff0c;对于固定的&#xff0c;若&#xff0c;则称 &#xff0c; 其中 为在条件下随机变量的条件分布律。 对于固定的&#xff0c;若&#xff0c;则称 &#xff0c; 其中 为在条件下随机变量的条件分布律。

内网穿透工具,如何保障安全远程访问?

内网穿透工具是一种常见的技术手段&#xff0c;用于在没有公网IP的情况下将本地局域网服务映射至外网。这种工具的使用极大地方便了开发人员和网络管理员&#xff0c;使得他们能够快速建立起本地服务与外部网络之间的通信渠道。然而&#xff0c;在享受高效快捷的同时&#xff0…

C语言之函数设计(1)

目录 没有返回值的函数 通用性 不含形参的函数 函数返回值的初始化 作用域 文件作用域 声明和定义 函数原型声明 头文件和文件包含指令 在上节中我们简单的学习了函数的创建方法&#xff08;函数定义&#xff09;与函数的使用方法&#xff08;函数调用&#xff09;&…

现代雷达车载应用——第2章 汽车雷达系统原理 2.2节 汽车雷达架构

经典著作&#xff0c;值得一读&#xff0c;英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.2 汽车雷达架构 从顶层来看&#xff0c;基本的汽车雷达由发射器&#xff0c;接收器和天线组成。图2.2给出了一种简化的单通道连续波雷达结构[2]。这…

Doris集群搭建——2.0.1.1版本

目录 一、启动Doris 二、配置并分发doris安装包和环境变量 1.分发doris安装包 2.解压安装包 3.分发环境变量 4.修改对应的配置文件 (1)修改be的配置 (2)修改fe的配置 三、be的扩容与缩容 (一)be扩容 1.添加be节点 3.另外两个节点启动be 4.重新查看be节点状态 (二…