我在HashMap
中有大约5-10M条目,我无法更改代码结构。我正在使用java
运行-Xms=512m -Xmx=1024m
。 HashMap
构造函数中要避免java.lang.OutOfMemoryError: GC overhead limit exceeded
的最佳容量/负载系数值是什么?
private final Map<String, ReportResultView> aggregatedMap = new HashMap<>(????, ????);
答案 0 :(得分:5)
<强>要点:强>
在这种情况下,加载因子可能似乎有趣,但它不能成为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或者其他)。默认的加载因子没问题。
很可能是导致问题的Entry
中存储的对象和对象HashMap
的实际大小。每个映射都有一个HashMap.Entry
对象,它包含键/值对和其他几个字段(例如哈希码,以及链接时指向下一个项目的指针)。这个Entry
对象本身consumes about 32 bytes - 当添加到底层数组条目的4个字节时,仅为条目的开销的<{1}}堆。然后你存储的实际对象也占用了空间:如果你的对象甚至有一些字段,它们至少与40 bytes * 10M entries = 400M
对象一样大,而你的堆已经非常耗尽。
您收到Entry
错误而不是GC limit exceeded
这一事实通常意味着您正在缓慢地接近堆限制,搅拌很多对象:GC往往会以这种方式失败场景,在空间不足之前。
因此,您很可能只需要为应用程序分配更多堆,找到存储更少元素的方法,或者减少每个元素的大小(例如,使用不同的数据结构或对象表示)。
[1] HotSpot上通常只有4个字节,即使在运行64位JDK时 - 尽管在某些64位平台上可能是8个字节如果 compressed oops被禁用某些原因。
[2]最糟糕的情况是,0.75加载因子意味着在调整大小后加载heap alloc failed
,因此您有0.75 / 2 = 0.375
个空元素,每个元素4个字节= ~25 MB。在重新散列期间,您可以添加另一个因子1.5左右,在最坏的情况下,因为旧的和新的后备阵列将同时在堆上。但是,当地图大小稳定时,这不适用。
[3]即使使用链接也是如此,因为通常使用链接不会增加内存使用(即,(1 - 0.375) * 10,000,000
元素已嵌入“下一个”指针,无论元素是否在链或不)。 Java 8使事情复杂化,因为Entry
工具得到了改进,大型链可能会转换为树,这可能会增加占用空间。
答案 1 :(得分:0)
要避免
java.lang.OutOfMemoryError: GC overhead limit exceeded
?
当hashmap调整大小时,需要重新分配内部表。因此,您需要为VM提供足够的内存来处理该临时副本或预先设置hashmap以防止重新调整大小。
您还可以查看来自https://github.com/boundary/high-scale-lib的hashmap实现,它应该提供较少破坏性的大小调整行为。