java获得map内存,Java 8 hashmap高内存使用率

I use a hashmap to store a QTable for an implementation of a reinforcement learning algorithm. My hashmap should store 15000000 entries. When I ran my algorithm I saw that the memory used by the process is over 1000000K. When I calculated the memory, I would expect it to use not more than 530000K. I tried to write an example and I got the same high memory usage:

public static void main(String[] args) {

HashMap map = new HashMap<>(16_000_000, 1);

for(int i = 0; i < 15_000_000; i++){

map.put(i, i);

}

}

My memory calulation:

Each entryset is 32 bytes

Capacity is 15000000

HashMap Instance uses: 32 * SIZE + 4 * CAPACITY

memory = (15000000 * 32 + 15000000 * 4) / 1024 = 527343.75K

Where I'm wrong in my memory calculations?

解决方案

Well, in the best case, we assume a word size of 32 bits/4 bytes (with CompressedOops and CompressedClassesPointers). Then, a map entry consists of two words JVM overhead (klass pointer and mark word), key, value, hashcode and next pointer, making 6 words total, in other words, 24 bytes. So having 15,000,000 entry instances will consume 360 MB.

Additionally, there’s the array holding the entries. The HashMap uses capacities that are a power of two, so for 15,000,000 entries, the array size is at least 16,777,216, consuming 64 MiB.

Then, you have 30,000,000 Integer instances. The problem is that map.put(i, i) performs two boxing operations and while the JVM is encouraged to reuse objects when boxing, it is not required to do so and reusing won’t happen in your simple program that is likely to complete before the optimizer ever interferes.

To be precise, the first 128 Integer instances are reused, because for values in the -128 … +127 range, sharing is mandatory, but the implementation does this by initializing the entire cache on the first use, so for the first 128 iterations, it doesn’t create two instances, but the cache consists of 256 instances, which is twice that number, so we end up again with 30,000,000 Integer instances total. An Integer instance consist of at least the two JVM specific words and the actual int value, which would make 12 bytes, but due to the default alignment, the actually consumed memory will be 16 bytes, dividable by eight.

So the 30,000,000 created Integer instances consume 480 MB.

This makes a total of 360 MB + 64 MiB + 480 MB, which is more than 900 MB, making a heap size of 1 GB entirely plausible.

But that’s what profiling tools are for. After running your program, I got

BwRHR.png

Note that this tool only reports the used size of the objects, i.e. the 12 bytes for an Integer object without considering the padding that you will notice when looking at the total memory allocated by the JVM.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: HashMap是基于哈希表实现的,它通过将键映射到桶中来实现快速查找。具体来说,当我们向HashMap中添加一个键值对时,它会先根据键的哈希值计算出该键值对应的桶的位置,然后将该键值对存储在该桶中。当我们需要查找某个键时,HashMap会先根据该键的哈希值计算出对应的桶的位置,然后在该桶中查找该键值对。如果该键值对存在,则返回其对应的值;否则返回null。HashMap还会根据负载因子来判断是否需要进行扩容,以保证哈希表的性能。 ### 回答2: Java中的HashMap是一种散列表的实现,用于存储键值对。它内部使用了数组来存储数据。 首先,HashMap内部有一个数组table,这个数组是具有固定长度的,长度一般默认为16。每个数组的位置称为桶(bucket),每个桶可以存储一个链表。当HashMap中插入一个键值对时,首先根据键的hashCode()方法计算出散列值,然后根据散列值计算出在数组中的位置,即桶的位置。 当插入键值对时,如果该桶为空,直接将键值对放入该位置,如果不为空,则需要进行链表的操作。当发生链表操作时,新插入的键值对将放在链表的开头或结尾。这样,在查找键值对时,可以从桶中的链表开始顺序查找。 HashMap中的键是唯一的,不允许重复。当插入一个键值对时,先通过hashCode()方法计算出散列值,然后通过equals()方法来比较键的唯一性。如果发现重复键,则会将原来的键值对替换为新的键值对。 在HashMap中,通过散列函数和桶的数据结构实现了效的键值对存储和查找。当需要查找一个键值对时,首先计算出散列值,并根据散列值找到对应的桶。然后,根据键的唯一性,再对桶中的链表进行顺序查找,最坏情况下的时间复杂度为O(n)。 需要注意的是,当HashMap的容量达到一定程度时,会发生扩容操作。扩容会重新计算散列值,再将键值对放入新的桶中,以保证散列函数的均匀性和桶的利用率。 总结来说,HashMap的内部是通过数组和链表实现的散列表,利用散列值和equals()方法来存储和查找键值对。它的优点是查找速度快,但在插入和删除操作时需要考虑到散列冲突和扩容问题。 ### 回答3: HashMapJava中常用的一种数据结构,它实现了Map接口,以键值对的形式存储数据。它的内部工作原理如下: 1. 哈希函数:HashMap内部使用哈希函数来将键映射到存储在数组中的索引位置。这个哈希函数可以根据键的特性进行自定义实现,通常是将键的哈希码进行运算,然后取模。 2. 数组和链表:HashMap的底层是一个数组,通常是一个固定大小的容量。数组的每个元素称为桶(bucket),每个桶可以存储一个或多个键值对。如果多个键值对映射到同一个桶位置,它们将以链表的形式存储,通过链表的方式解决哈希冲突。 3. 存储和获取:当往HashMap中存储键值对时,首先根据键的哈希码计算出对应的桶位置,然后判断该桶是否已经存在元素,若存在,则以链表形式存储新的键值对。如果桶为空,则直接将键值对存入该桶中。当需要获取某个键对应的值时,通过哈希函数计算出桶的位置,然后遍历该桶的链表,如果找到对应的键值对,则返回值。 4. 扩容和再散列:在插入键值对时,如果哈希冲突导致链表过长,会增加遍历的时间复杂度。为了保证性能,在 HashMap 内部会维护一个装载因子(默认是 0.75),当链表的长度超过装载因子所设定的阈值,则会触发扩容操作。扩容会创建一个新的更大的数组,将原数组中的数据重新计算哈希值并分配到新的桶中。这个过程称为再散列。 5. 如何选择合适的哈希函数和容量:选择合适的哈希函数可以提键分布的均匀性,减少哈希冲突的发生。容量的选择也很重要,如果容量太小,哈希冲突的概率会增大;如果容量过大,则会浪费内存。在Java 8之后,HashMap引入了红黑树的概念,当链表的长度达到一定阈值(默认为8)时,会将链表转换为红黑树,以提查找效率。 总的来说,HashMap内部使用哈希函数将键映射到数组中,并采用链表和红黑树的方式解决哈希冲突。随着数据的增加,HashMap会自动扩容并重新分配键值对的位置。了解HashMap的内部工作原理可以帮助我们更好地理解它的性能特点,并且能够在使用时选择合适的容量和哈希函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值