前言
前两天在一个帖子中看到一道面试题:堆内存超过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开发界的小学生,文章可能不是很优质,但肯定会很用心。
之前始终有本人写博客的想法,但无奈真的太迁延了,说懒说躺平都行,而且我也不是说真就很忙,忙到连码字的工夫都没有了,都是本人给本人找的借口。
但本人其实也在不停地学习,奈何始终没有系统性地整顿过,作为萌新,我真心感觉写博客跟其余大佬们交换真的是一件坏事,很多货色其实你本人刚开始也不懂,但你为了整顿成文章就须要去理解和学习更多,而且如果你能把这些常识传授给他人,他人也能听懂,那我感觉这些常识才是真的跟你本人死记硬背了。
的确真的不能再这样上来了,肯定要克服本人的惰性,精力治理真的很重要,健身也要从新捡起来。
大家如果有什么倡议或者文章有什么问题也都能够提出来,一起独特加油~