java引用类型及指针压缩
Java中引用类型所占字节
如,我声明了一个对象,它在栈中就先有了个空间,(用来放地址引用的),这个空间的大小是多少?
java规范中并没有强行定义虚拟机中任何一种类型在虚拟机中所占用内存的具体大小,但是规范规定了每种类型的取值范围。从这种角度上看,每种类型会有一个最小位宽,或者内存占用大小。
而且java虚拟机规定中,在方法区中所占用的内存大小与在栈帧所占用的内存大小不同,因为在方法区中占用内存以字节为最小单位,但是在战帧中以字为最小单位。如byte类型在方法区中它占用8位,为一个字节,但是在栈帧中以一个字,即32位来处理,其实就是当作一个int类型来处理。
**引用类型,其位宽与int型一样,在方法区中它占用32位,4个字节,在栈帧中占用一个字。
**但是虚拟机实现者可以扩大这种内存占用量,因为虚拟机规定只要满足取值范围即可,并没有规定非要32位一个字才行。
引用本身的大小和操作系统的位数有关
64位平台上,占8个字节,在32位平台上占4个字节,这个应该是很自然的事情,因为32-bit的操作系统,在4G(2^32)的内存空间内找到某个地址,这个地址是用4bytes(32bits)来表示的。
引用类型的大小之所以会有所不同,主要是由于以下原因:
- 地址空间大小:32位和64位JVM的地址空间不同,这直接影响了引用类型的大小。
- 内存效率:在64位JVM上使用压缩引用可以节省内存,这对于具有大量对象引用的应用程序尤其重要。
- JVM实现:不同的JVM实现可能会有不同的优化策略,这可能会影响引用类型的大小。
优化策略:指针压缩
1|0对象内存结构
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:
1|11.对象头:
Hotspot 虚拟机的对象头包括两部分信息:
第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等)
另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
其实还有一个数组长度
对象是数组的情况下,才有这部分数据。对象不是数组,则没有这部分数据,不会为其分配空间。
1|22.实例数据:
实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
对象里的非静态属性占用的空间(包括父类的所有属性,不区分修饰类型),不包括方法,注意:是非静态属性,属于对象的属性,静态属性是属于类的不在对象上分配空间。如果属性是基本数据类型,则直接存对象本身,如果是引用类型,则存的是对象的指针。
1|33.对齐填充:
对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍。比如对象头+实例数据大小只有30字节,则需要补齐到32字节,换句话说就是对象的大小必须是 8 字节的整数倍。因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
2|0指针压缩:
计算机操作系统分32位和64位,即用32个(或64个)二进制0和1的组合来表示内存地址。
以32位为例,在普通的内存中,通过0和1的排列组合,能够表示寻址的内存空间最大就是2^32个,换算成内存空间就是2 ^ 32 / 1024 / 1024 / 1024 = 4G,也就是说32位的操作系统最大能寻址的内存空间只有4G,同理,64位的操作系统(查阅资料显示其实没有用到64位,最多只用到了48位,这个可自行查阅资料,反正肯定比32位大的多)2 ^ 48 / 1024 / 1024 / 1024 / 1024 = 256TB,这样内存就足够大了,但是目前还没有厂商能生产出这么大的内存。
4G对于现在的java应用系统来说,内存已经算小的了,那我们就会想到使用64位的系统,这样内存就可以更大了,但是当我们准备将32位系统切换到64位系统,起初我们可能会期望系统性能会立马得到提升,但现实情况可能并不是这样的,为什么呢?
(1)32位系统对象指针是4字节,64位系统对象指针是8字节(1位表示1bit,8个bit表示1字节),这样64位系统中的对象引用占用的内存空间是32位系统中的两倍大小,因此间接的导致了在64位系统中更多的内存消耗以及更频繁的GC发生,GC占用的CPU时间越多,那么我们的应用程序占用CPU的时间就越少,响应会变慢,吞吐量会降低。
(2)对象的引用变大了,那么CPU可缓存的对象相对就少了,降低了CPU缓存命中率,增加了对内存的访问,CPU对CPU缓存的访问速度可比对内存的访问速度快太多了,所以大量的对内存访问,会降低CPU的执行效率,增加了执行时间,从而影响性能。既然32位系统内存不够,64位内存够但又影响性能,那有没有折中方案来解决这两个问题呢,于是聪明的JVM开发者想到了利用压缩指针,在64位的操作系统中利用32位的对象指针引用获得超过4G的内存寻址空间。
3|0如何指针压缩?
由于在JVM里,对象都是以8字节对齐的即对象的大小都是8的倍数,所以不管用32位还是64位的二进制表示,末尾3位始终都是0。
例如:8 = ...00001000, 16 = ...00010000,24 = ...00011000
既然JVM已经知道了这些对象的内存地址后三位始终是0,那么这些无意义的0就没必要在堆中继续存储。相反,我们可以利用存储0的这3位bit存储一些有意义的信息,这样我们就多出3位bit的寻址空间,也就是说如果我们继续使用32位来存储指针,只不过后三位原本用来存储0的bit现在被我们用来存放有意义的地址空间信息,当寻址的时候,JVM将这32位的对象引用左移3位即可(后三位补0)。我们原本32位的内存寻址空间一下变成了35位,可寻址的内存空间变为2 ^ 35 / 1024 / 1024 / 1024 = 32G,也就是说在64位系统JVM的内存可扩大到32G了,基本上可满足大部分应用的使用了。
所以在64位系统下,通过压缩指针我们可以继续使用32位来处理(引用指针由8字节可降低到4字节),存储的时候右移3位,寻址的时候左移3位,如下图所示。
这样一来,JVM虽然额外的执行了一些位运算但是极大的提高了寻址空间,并且将对象引用占用内存大小降低了一半,节省了大量空间,况且这些位运算对于CPU来说是非常容易且轻量的操作,可谓是一举两得。
4|0常见数据大小
在64位机器上,
开启指针压缩(-XX:-UseCompressedOops)的情况下,对象头占用12bytes
不开启指针压缩(-XX:+UseCompressedOops)则占用16bytes
对象引用(reference)类型在64位机器上,关闭指针压缩时占用8bytes, 开启时占用4bytes。
例子:一个包含两个属性的对象:int和byte,并不是占用17bytes(12+4+1),而是占用24bytes(对17bytes进行8字节对齐)
数组对象的对象头占用24 bytes,启用压缩后占用16字节。比普通对象占用内存多是因为需要额外的空间存储数组的长度。
int[10]:
开启压缩:16 + 10 * 4 = 56 bytes;
关闭压缩:24 + 10 * 4 = 64bytes。
5|0如何进一步扩大寻址空间
前边提到在Java虚拟机堆中对象起始地址均需要对其至8的倍数,不过这个数值我们可以通过JVM参数-XX:ObjectAlignmentInBytes 来改变(默认值为8)。当然这个数值的必须是2的次幂,数值范围需要在8 - 256之间。正是因为对象地址对齐至8的倍数,才会多出3位bit让我们存储额外的地址信息,进而将4G的寻址空间提升至32G。
同样的道理,如果我们将ObjectAlignmentInBytes的数值设置为16呢?
对象地址均对齐至16的倍数,那么就会多出4位bit让我们存储额外的地址信息。
寻址空间变为2 ^ 36 / 1024 / 1024 / 1024 = 64G。
通过以上规律,我们就能知道,在64位系统中开启压缩指针的情况
寻址范围的计算公式:4G * ObjectAlignmentInBytes = 寻址范围
但是并不建议大家贸然这样做,因为增大了ObjectAlignmentInBytes虽然能扩大寻址范围,但是这同时也可能增加了对象之间的字节填充,导致压缩指针没有达到原本节省空间的效果。
6|0Java压缩指针参数
关于压缩指针的两个参数:
7|0实例:
使用JOL工具分析Java对象
maven依赖:
常用方法:
查看对象内部信息: ClassLayout.parseInstance(obj).toPrintable()
查看对象外部信息:GraphLayout.parseInstance(obj).toPrintable()
查看对象占用空间总大小:GraphLayout.parseInstance(obj).totalSize()
查看类内部信息:ClassLayout.parseClass(Object.class).toPrintable()
__EOF__

本文链接:https://www.cnblogs.com/chenlei210162701002/p/18381320.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具