6

我遇到了一个问题,我找不到解决方案。我正在使用 HashSet 来存储值。我存储的值是自定义类型 Cycles,其中我已经覆盖了 HashCode 并等于以下内容,以确保低速性能不受 hascode 或 equal 方法的影响另外我已将哈希集的初始容量设置为 10.000 .000

@Override
public int hashCode() {
 final int prime = 31;
 int result = 1;
 result = prime * result + (int) (cycleId ^ (cycleId >>> 32));
 return result;
}

@Override
public boolean equals(Object obj) {
 if (this == obj)
 return true;
 if (obj == null)
 return false;
 if (getClass() != obj.getClass())
 return false;
 Cycle other = (Cycle) obj;
 if (cycleId != other.cycleId)
 return false;
 return true;
}

在第一个 1.500.000 个值之后,当我尝试添加一个新值(使用 HashSet 类的 add 方法)时,程序非常慢。最终,在存储的值达到 1.600.000 之前,我将遇到 java 内存不足异常(线程“Thread-0”java.lang.OutOfMemoryError:Java 堆空间中的异常)

我使用的 IDE 是 Eclipse。所以下一步是将 JVM 堆大小从默认值增加到 1 giga(使用命令 Xmx1000M 和 Xms1000M)现在椭圆开始时可用内存增加 10 倍(我可以在右下角看到总堆大小显示内存和已用内存)但我再次具有与以前相同的值(在 1.500.000 之后和 1.600.000 之前)相同的“慢”性能和相同的内存不足错误,这很奇怪。

有谁知道这可能是什么问题?

先感谢您

4

9 回答 9

10

您不想为 Eclipse 增加 JVM 堆,而是想为您的程序设置它。

转到Run > Run Configurations(或Debug Configurations)并在那里设置VM 选项

于 2010-07-25T12:56:06.100 回答
4

没有足够的堆内存(通过 -Xmx 增加它,例如-Xmx512m)。当可用内存变得非常低时,垃圾收集器会花费大量时间,它会疯狂地扫描堆以查找无法访问的对象。

您的 hashCode() 很好,使用long的所有位的额外积分。cycleId

编辑. 现在我看到你确实增加了内存,并没有帮助。首先,您确定您确实设法增加了内存吗?您可以通过 jconsole 进行检查,连接到您的应用程序并查看其堆大小。

对于要验证的替代解释,您是否有任何特定模式cycleId可能使这个 hashCode() 实现变得糟糕?就像,它的 32 个高位与 32 个低位基本相似。(是的,正确的)。

但不是。即使是这种情况,您也会看到性能逐渐下降,而不是在特定点急剧下降(并且您确实会遇到 OutOfMemoryError 和疯狂的 gc 操作)。所以我最好的猜测仍然是内存问题。你要么没有像你想象的那样增加堆大小,要么在某个时候有一些其他的代码占用了内存。(您可以使用 VisualVM 之类的工具对其进行分析,并在 OOME 上获取堆转储,并查看其中包含哪些对象)。

Edit2我把上面的正确部分加粗了。

于 2010-07-25T12:22:44.477 回答
2

从 Eclipse 启动的应用程序可用的内存大小应该从 Run 菜单中配置。尝试:

运行 -> 运行配置 -> 参数 -> 虚拟机参数 -> -Xmx1000M

您的程序运行缓慢的原因是垃圾收集器 - 每次内存超出限制时它都会启动。

于 2010-07-25T11:59:20.583 回答
2

您是否测试过您的hashCode方法实现?31对于 的任何值,它总是返回circleId。您的 HashMap 运行缓慢并不奇怪,它具有线性性能。

于 2010-07-25T12:02:46.157 回答
1

如果你想增加你的程序可以使用的内存,它不会帮助增加 Eclipse 的堆大小。您必须将该参数放入程序的启动配置的 vm 参数中。

于 2010-07-25T12:01:35.847 回答
1

JVM 抛出“内存不足”不是基于可用内存。当花费在垃圾收集上的时间太多时,它会被抛出。检查这个。确切的实现细节因 JVM 和垃圾收集器实现而异。

在这种情况下,增加内存无济于事。您可能不得不选择另一种方法。

于 2010-07-25T12:09:05.680 回答
0

也许您的计算机没有足够的内存,因此它必须交换到磁盘。

于 2010-07-25T11:54:16.070 回答
0

你是如何初始化你的HashSet?你需要了解它的增长模式。每次add操作时,它都会检查它是否接近容量。如果它达到某个点(由其“负载因子”确定),它会执行一个代价高昂的调整大小操作。从 JavaDoc (的HashMap- 支持的集合HashSet):

作为一般规则,默认负载因子 (.75) 在时间和空间成本之间提供了良好的折衷。较高的值会减少空间开销,但会增加查找成本(反映在 HashMap 类的大多数操作中,包括 get 和 put)。在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以尽量减少重新哈希操作的次数。如果初始容量大于最大条目数除以负载因子,则不会发生重新哈希操作。

于 2010-07-25T12:36:14.783 回答
0

I'm pretty disappointed at the number of answers telling the OP to increase his heap size in his application. That's not a solution--that's a quick-and-dirty patch, which won't address any underlying problem.

I found this presentation extremely informative: http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf

Mainly the page listing the minimal byte sizes of each when empty--

ArrayList: 40 or 48
LinkedList: 48
HashMap: 56 or 120
HashSet: 72 or 136

Turns out that a HashSet is practically a HashMap, and (counterintuitively) takes up more more memory despite holding only values instead of key-value pairs.

于 2014-04-24T23:48:06.597 回答