为什么JVM要用到压缩指针?Java对象要求8字节的整数倍?

createh51个月前 (01-31)技术教程17

作者:苏易困

原文链接:
https://juejin.cn/post/7076013091040788494

前言

前两天在一个帖子中看到一道面试题:
堆内存超过32G时,为什么压缩指针失效?

之前没有了解过这方面的知识,于是开始google起来,但当我翻看了不下一页的帖子,我都仍然没有搞懂,因为好多答案给我的感觉更像是:
因为堆内存超过32G,压缩指针会失效,所以堆内存超过32G时,压缩指针会失效。


我:???

本着有问题搞不懂就吃不下冰激凌的原则,我决定搞清楚这个问题。

32位和64位

首先我们都知道知道操作系统有32位操作系统(别名 x86 )和64位操作系统(别名 x86-64 或 x64),相对的JVM也分为32位和64位。

什么?你没注意过?

如何知道现在自己使用的JVM是32位还是64位?

命令行输入:java -version
如果你是64位的话,会出现 64-Bit 这样的内容:

java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
复制代码

如果你是32位的话,则没有相关的内容:

java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) Client VM (build 25.211-b12, mixed mode)
复制代码

32位操作系统只可以运行32位的JVM,64位操作系统则都可以运行(有点类似兼容老版本)。

那我应该如何选择多少位的JVM呢?

32位JVM的寻址空间只有4G(2^32),也就是你的java进程最大只能使用4G内存(因为有其他开销,实际远小于4G);而64位JVM的寻址空间最大有2^64,差不多可以理解为无限大


(不同操作系统和JVM对应的 进程最大内存 和 最大堆内存)
(Approx:大概,approximately的缩写)

64位的JVM听起来是升级版,性能要比32位的JVM要高吧?

其实恰恰相反,64位JVM的寻址空间更大了,但是会带来性能的损耗;同样的应用,运行在64位JVM上 比起 运行在32位JVM上 会有0~20%的性能损耗(取决于应用里面指针的数量)。

其实从名字上我们可以就可以区分,64位JVM,他的每一个native指针都占用64位(即64bit,也就是8字节)。32位JVM则只有4字节。 加载这些额外的字节也自然会影响内存的占用。

铺垫这么多,指针的寻址原理奉上

假如我们这里有3个对象A、B、C,他们的大小分别为8、16、8字节(为什么假设这几个值,我们先按下不表),并且在内存中连续存储,为更好理解,我们简化为下图:


指针用 内存位置 来标记对象在内存中的位置:
A:00000000 00000000 00000000 00000000 (十进制表示:0)
B:00000000 00000000 00000000 00001000 (十进制表示:8)
C:00000000 00000000 00000000 00011000 (十进制表示:24)

从上面可以看出32位的指针,满打满算也只能存储2^32,约4GB的内存地址。
如果是64位的指针,就能表示2^64,上面说的可以理解为无限大,如果你有听过 国际象棋盘与麦粒 的故事就知道,它肯定能满足需求,甚至感觉还有点浪费。

回归题目,压缩指针的引入。

既然64位指针用来存储太浪费了,有什么更好的办法可以在32位的限制下表示更多的内存地址吗?
这时,我们发现对象A、B、C大小都是8字节的整数倍,即8是他们对象大小的最大公约数!

我这边就不卖关子,直接说答案,我们可以借助索引来标识。
用 8位内存地址偏移量 代表 1索引
那么A的位置就可以标识为 索引0,B为 索引1,C为 索引3。


指针用 索引 来标记对象在内存中的位置:
A:00000000 00000000 00000000 00000000 (十进制表示:0)
B:00000000 00000000 00000000 00000001 (十进制表示:1)
C:00000000 00000000 00000000 00000011 (十进制表示:3)

加入索引这一概念是为了方便理解;实际上JVM是通过读取时左移3位,存储时右移3位来完成的。
也就是说原本可表示4GB的内存地址,因为1索引表示8个内存地址偏移量,现在可以表示最高存储32GB的内存地址了。

伏笔回收:Java对象的大小为什么必须是8字节的整数倍?

上面的对象A、B、C我们假设的大小是8字节、16字节、8字节;共同点你可能发现了,他们都是8字节的倍数,其实Java对象的大小就必须是8字节的整数倍,如果没有这个条件,上面说的索引说法也不成立。
当然除了为了支持上面这些功能外,另外还有的就是因为现在大多数计算机都是高效的64位处理器,顾名思义,一次能处理64位的指令,即8个字节的数据,HotSpot VM的自动内存管理系统也就遵循了这个要求,这样子性能更高,处理更快。

JVM如何保证Java对象的大小都是8字节的整数倍?

用一个普通的Java对象举个简单的栗子

在JVM中,Java对象保存在堆中,由三部分组成:
1.对象头 (Object Header)
2.示例数据(Instance Data)
3.对齐填充(Padding)


如果你还不了解的话也没关系,我们讨论的只牵扯到最后一个部分,也就是 对齐填充。
对象可以有对齐填充,也可以没有;如果一个对象的前两部分所占大小不是8字节的整数倍,比如12字节,那么对齐填充会以此来填充对象大小到8字节的整数倍,即对齐填充占4字节,java对象一共16字节。

对齐填充:8字节的倍数就由我来组成!

有无压缩指针的区别

把64位JVM的指针压缩为32位,即引入压缩指针的原因是为了节省内存,但其实64位JVM的指针本来就可以是64位。

从8字节压缩到4字节,听起来好像才少了4个字节,但要知道,因为Java对象要补齐8字节的倍数;假如一个Java对象刚好满足了8字节整数倍,但因为没有压缩指针多出来4字节,这时又因为要补齐,还需要再补上4字节,一个对象就多了8字节!听起来好像还不怎么多,但这可是一个对象就少8字节,如果是一个大项目,差的可能就不是一星半点了。

所以肯定是开比不开好(JDK1.6的版本后,64位的JVM默认情况下是开启指针压缩的。)

冰激凌

我的心也终于放下了,马上下楼买冰激凌吃~

相关文章

两成大数据岗位要求Java技能(大数据需要java什么水平)

受新基建、数字化转型及数字中国愿景目标等一系列政策促进,大数据产业高速增长,并与人工智能、云计算和区块链等领域深度融合,为各行各业带来革命性的变革。相关人才需求量正在持续增长,且呈现出供不应求的趋势。...

一文搞懂消息推送技术选型(消息推送原理以及实现过程)

Ajax短轮询MQredis 订阅/发布Ajax短轮询优点:简单高效、浏览器使用循环不断地、间隔地发送请求获取数据缺点:频繁创建/断开连接,每次请求都会查询一遍数据不管有无都返回,对服务器业务处理的性...

Java 16 正式发布,新特性一一解析

3 月 16 日,甲骨文正式发布 Java 16。甲骨文表示,现在为所有开发人员和企业提供 Java 16。据悉,按照甲骨文重要补丁更新(CPU)时间表,甲骨文 JDK 16 将至少获得两次季度更新。...

Java 正式发布 | 历史上的今天(java的前世今生)

整理 | 王启隆透过「历史上的今天」,从过去看未来,从现在亦可以改变未来。今天是 2023 年 5 月 23 日,在 2007 年的今天,盛大宣布出售所持有新浪公司股票,累计获利 7650 万美元。盛...

Java真的没出路了吗?(java真的没出路了吗为什么)

你以为的Java,烂大街了已经很饱和了,找不到工作。实际上的Java,虽然软件开发行业语言种类很多,包括Java、前端、Python、C++、大数据等等,但是Java工程师的需求量占据了软件开发工程师...