java - 5-10M 条目的最佳 hashmap 容量/负载因子是多少?

标签 java dictionary optimization hashmap java-8

我在 HashMap 中有大约 5-10M 个条目,我无法更改代码结构。我正在使用 -Xms=512m -Xmx=1024m 运行 javaHashMap 构造函数中的最佳容量/负载因子值是多少,以避免 java.lang.OutOfMemoryError: GC overhead limit exceeded

private final Map<String, ReportResultView> aggregatedMap = new HashMap<>(????, ????);

最佳答案

总结: 在这种情况下,加载因子可能看起来很有趣,但它不可能是您的 OOME 的根本原因,因为加载因子仅控制浪费的后备阵列空间,并且默认情况下(加载因子为 0.75)仅消耗 ~2.5% 的堆(并且不会导致高对象数 GC 压力)。更有可能的是,您存储的对象及其关联的 HashMap.Entry 对象使用的空间已占用堆。

详细信息: HashMap 的加载因子控制映射使用的底层引用数组的大小。较小的加载因子意味着给定大小的空数组元素较少。所以一般来说,增加负载因子会减少内存使用,因为空数组槽较少。3

但是,您不太可能通过调整负载因子来解决 OOME。然而,空数组元素只会“浪费”4 个字节1。因此,对于 5M-10M 元素的数组,加载因子 0.75(默认值)将浪费大约 25 MB 的内存2

这只是您分配的 1,024 MB 堆内存的一小部分,因此您无法通过调整加载因子来解决 OOME(除非您使用了一些非常愚蠢的东西,比如极低的负载系数为 0.05 左右)。默认负载因子就可以了。

很可能是对象的实际大小和存储在 HashMap 中的对象 Entry 导致了问题。每个映射都有一个HashMap.Entry 对象,该对象保存键/值对和一些其他字段(例如哈希码,以及链接时指向下一项的指针)。此 Entry 对象本身 consumes about 32 bytes - 当添加到基础数组条目的 4 个字节时,40 字节 * 10M 条目 = 400M单独条目的开销。然后,您存储的实际对象也会占用空间:如果您的对象甚至只有少量字段,它们至少会与 Entry 对象一样大,并且您的堆几乎已经用完了。

您收到 GC limit exceeded 错误而不是 heap alloc failed 的事实通常意味着您正在缓慢接近堆限制,搅动大量对象:在这种情况下,GC 往往会在空间用完之前以这种方式失败。

因此,很可能您只需要为您的应用程序分配更多堆,找到一种存储更少元素的方法,或者减少每个元素的大小(例如,使用不同的数据结构或对象表示)。


[1] 在 HotSpot 上通常是 4 个字节,即使在运行 64 位 JDK 时也是如此 - 尽管在某些 64 位平台上它可能是 8 个字节if compressed oops由于某种原因被禁用。

[2] 最坏的情况,0.75 负载因子意味着在调整大小后加载 0.75/2 = 0.375,因此您有 (1 - 0.375) * 10,000,000 空元素,每个元素 4 个字节 = ~25 MB。在重新散列期间,您可以添加另一个 1.5 左右的因子,在最坏的情况下,因为旧的和新的后备数组将同时在堆上。但是,本地图大小稳定时,这不适用。

[3] 即使使用链接也是如此,因为通常使用链接不会增加内存使用(即 Entry 元素已经嵌入了“下一个”指针,无论元素是否在链中)。 Java 8 使事情变得复杂,因为改进了 HashMap 实现,这样大型链可以转换为树,这可能会增加占用空间。

关于java - 5-10M 条目的最佳 hashmap 容量/负载因子是多少?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39657239/

相关文章:

java - 后台模式与关闭模式

java - 在 jena 中以通用方式查询 RDF 模型的最佳方法是什么? (sparql 或使用集合和迭代器)

java - 编写一个通用方法来对一组任意类型的可比较对象进行排序

python - 如何将嵌套字典转换为带有键顺序的列表

javascript - 在 JavaScript ReactJS 中映射对象数组时出错

java - Java 编译器是否有效地处理内联字符串?

python - Python字典对对象方法的理解

java - 数据在 for 循环中被覆盖以获取新值和以前的值

java - 优化 SSE 代码

C++ map 输出格式化垃圾