一、map<Long,Long> resultCache = new HashMap<Long,Long>(1000000),100w数据存满时,粗估resultCache大约占多少内存?
Java中HashMap
的内存占用取决于多个因素,包括键值对的数量、负载因子、容量以及每个对象的内存占用。对于HashMap<Long, Long>
,我们可以大致估算其内存占用。
假设我们有一个HashMap<Long, Long>
,其初始容量设置为100万(1,000,000),默认负载因子为0.75。
- 每个
Long
对象占用内存:在64位JVM上,一个Long
对象通常占用24字节(16字节对象开销 + 8字节数据)。 - HashMap节点占用内存:每个HashMap节点(HashMap.Node)大约占用16字节(对象头部分)+ 8(hash)+ 8(key引用)+ 8(value引用)+ 8(next引用)= 48字节。
- 数组占用:
HashMap
内部使用数组存储节点,数组的每个槽位占用8字节(64位系统下的引用大小)。
假设HashMap
的容量扩展到足以容纳100万个键值对,实际容量会是大于或等于100万的最小2的幂次,即1048576(2^20)。因此,数组部分大约占用8 * 1048576 = 8388608字节(8MB)。
接下来,计算节点和对象的总占用:
- 每个键值对占用:24(key的Long对象)+ 24(value的Long对象)+ 48(节点)= 96字节。
- 100万个键值对占用:96 * 1000000 = 96000000字节(约 91.6 MB)。
因此,总的大致估算内存占用为数组部分的 8MB 加上键值对部分的 91.6 MB,总共约 99.6 MB。 这还不包括Java虚拟机可能为了优化分配和管理内存而额外使用的内存。
请注意,这只是一个粗略的估算,实际内存占用可能会因JVM的实现、垃圾收集器的行为、内存对齐以及其他因素而有所不同。
二、在64位JVM上,一个Long对象通常占用24字节(16字节对象开销 + 8字节数据)。
在64位JVM上,一个Long
对象的内存占用确实通常为24字节。这个计算来源如下:
-
对象头(Object Header): 12字节
- 标记字(Mark Word): 8字节
- 类型指针(Klass Pointer): 4字节
-
实例数据(Instance Data): 8字节
long
值: 8字节
-
对齐填充(Padding): 4字节
- 为了满足8字节对齐要求
计算过程:
- 12(对象头)+ 8(实例数据)= 20字节
- 20字节不是8的倍数,所以需要填充4字节
- 最终:20 + 4 = 24字节
这种内存布局是HotSpot JVM在默认情况下的典型实现。但需要注意:
- 实际内存占用可能因JVM实现和配置而异。
- 使用压缩指针(-XX:+UseCompressedOops,默认开启)可能会改变这个计算。
- 某些JVM优化可能会影响对象的内存布局。
因此,24字节是一个常见的估算值,但在特定环境下可能会有所不同。
三、HashMap节点占用内存:每个HashMap节点(HashMap.Node)大约占用16字节(对象头部分)+ 8(hash)+ 8(key引用)+ 8(value引用)+ 8(next引用)= 48字节。
在64位JVM上,即使启用了压缩指针(Compressed Oops),对象头通常是由两部分组成:一个8字节的标记字(Mark Word)和一个4字节的类型指针。但是,由于对象的大小需要按照8字节对齐,所以实际上对象头在很多情况下会被视为占用16字节,以满足对齐要求。
对于HashMap.Node
对象的内存布局,我们应该这样考虑:
-
对象头: 通常是16字节(包括对齐)
- 标记字(Mark Word): 8字节
- 类型指针(Klass Pointer): 4字节
- 对齐填充(如果需要): 4字节,以确保对象头加上类型指针总共占用16字节,满足8字节对齐的要求。
-
实例数据:
int hash
: 4字节 + 4字节填充K key
: 8字节(引用)V value
: 8字节(引用)Node<K,V> next
: 8字节(引用)
计算过程:
- 对象头: 16字节
- 实例数据: 4+4(int hash)+ 8(K key引用)+ 8(V value引用)+ 8(Node<K,V> next引用)= 32字节
- 总计:16(对象头)+ 32(实例数据)= 48字节
四、数组占用:HashMap
内部使用数组存储节点,数组的每个槽位占用8字节(64位系统下的引用大小)。
在64位操作系统上:
-
未开启压缩指针:在64位JVM上,如果未开启压缩指针,一个对象引用通常占用8字节。这是因为64位系统可以使用的地址空间远大于32位,所以需要更多的位来表示内存地址。
-
开启压缩指针:为了减少内存使用,提高性能,JVM引入了压缩指针的概念。当开启压缩指针时,即使在64位系统上,对象引用可以被压缩到4字节。这是通过限制堆的最大大小(例如,小于32GB),使得不需要完整的64位来寻址每个对象,从而实现的。
这是通过限制堆的最大大小(例如,小于32GB),使得不需要完整的64位来寻址每个对象,从而实现的。解释:
这个概念涉及到JVM中压缩指针(Compressed Oops)的工作原理:-
地址空间:
在64位系统中,理论上可以使用64位来表示内存地址,这意味着可以寻址高达2^64字节(约16EB)的内存空间。 -
实际需求:
但实际上,大多数应用程序不需要如此大的内存空间。通常,几十GB的堆内存就足够了。 -
压缩指针的原理:
JVM通过限制堆的最大大小(例如32GB),可以用更少的位来表示对象的地址。 -
如何实现:
- 假设堆的大小限制在32GB以内。
- 32GB = 2^35 字节
- 这意味着只需要35位就可以表示堆中的任何地址。
-
对齐和优化:
- JVM进一步将对象按8字节对齐。
- 这意味着对象的地址总是8的倍数。
- 因此,地址的最后3位总是000。
-
结果:
- 实际上只需要32位(35 - 3 = 32)就可以表示堆中的任何对象地址。
- 32位正好可以存储在一个4字节的整数中。
-
好处:
- 引用(指针)从8字节减少到4字节。
- 这不仅节省了内存,还提高了缓存效率。
通过这种方式,JVM能够在64位系统上使用32位(4字节)的引用,而不是完整的64位(8字节),从而大大减少了内存使用,同时保持了对较大堆(最多 32GB)的支持。
-
因此,当我提到数组的每个槽位占用8字节时,是基于未开启压缩指针的假设。如果开启了压缩指针,并且堆的大小允许,那么每个槽位的引用大小可以减少到4字节。
在实际应用中,是否开启压缩指针以及选择的堆大小,将直接影响到对象引用的大小,从而影响到整个HashMap
的内存占用。对于大多数现代JVM实现,默认情况下是开启压缩指针的,但这也取决于具体的JVM配置和版本。