我已经实现了一个java程序。这基本上是一个具有固定线程数的多线程服务。每个线程一次执行一个任务,创建一个hashSet,hashset的大小可以在单个hashset中从10到20,000个项目变化。在每个线程结束时,结果将使用synchronized。
添加到共享集合List问题发生在某些时候我开始失去内存异常。在进行了一些研究之后,我发现当GC忙于清除内存时会发生这种内存异常,此时它会阻止整个世界执行任何操作。
请给我如何处理如此大量数据的建议。 Hashset是否是一个正确的数据结构?如何处理内存异常,我的意思是一种方法是使用System.GC(),这又不好,因为它会减慢整个过程。或者我可以在将其添加到共享集合列表后处置“HashSet hsN”吗?
请让我知道你的想法并指导我,无论我哪里出错。这项服务将处理大量的数据处理。
由于
//business object - to save the result of thread execution
public class Location{
integer taskIndex;
HashSet<Integer> hsN;
}
//task to be performed by each thread
public class MyTask implements Runnable {
MyTask(long task) {
this.task = task;
}
@Override
public void run() {
HashSet<Integer> hsN = GiveMeResult(task);//some function calling which returns a collection of integer where the size vary from 10 to 20000
synchronized (locations) {
locations.add(task,hsN);
}
}
}
public class Main {
private static final int NTHREDS = 8;
private static List<Location> locations;
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(NTHREDS);
for (int i = 0; i < 216000; i++) {
Runnable worker = new MyTask(i);
executor.execute(worker);
}
// This will make the executor accept no new threads
// and finish all existing threads in the queue
executor.shutdown();
// Wait until all threads are finish
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
对于这样的实现,JAVA是最佳选择还是C#.net4?
答案 0 :(得分:5)
我可以看到几个问题:
您在MyTask
对象上进行同步,该对象是为每次执行单独创建的。您应该在共享对象上进行同步,最好是您正在修改的对象,即locations
对象。
216,000次运行,再乘以10,000个返回的对象,每个Integer
对象最少12个字节,大约是24 GB 的内存。您是否在计算机上拥有那么多物理内存,更不用说JVM了?
32位JVM的堆大小限制小于2 GB。另一方面,在64位JVM上,Integer
对象大约需要16个字节,这会将内存需求提高到30 GB以上。
有了这些数字,你得到OutOfMemoryError
......
PS:如果你做有那么多可用的物理内存并且仍认为你做的是正确的事情,你可能想看看{{ 3}}
修改强>
即使JVM可以使用25GB的内存,它仍然可以推动它:
每个Integer
对象在现代64位JVM上需要16个字节。
您还需要一个8字节引用,指向它,无论您使用哪种List
实现。
如果您使用的是链表实现,则每个条目的列表条目对象的开销也至少为24字节。
充其量你可能希望以25GB存储大约1,000,000,000个Integer
个对象 - 如果你使用的是链接列表的一半。这意味着每个任务平均不会产生超过5,000个(分别为2,500个)对象,而不会导致错误。
我不确定您的确切要求,但您是否考虑过返回更紧凑的物体?例如,从每个int[]
生成的HashSet
数组只保留每个结果最少4个字节,而不会产生对象容器开销。
编辑2:
我刚刚意识到您将HashSet
个对象本身存储在列表中。 HashSet
个对象在内部使用HashMap
,然后使用每个条目的HashMap.Entry
个对象。在64位JVM上,除了存储的对象之外,入口对象还消耗大约40个字节的内存:
指向Integer
对象的关键引用 - 8个字节。
值引用(HashSet中始终为null
) - 8个字节。
下一个参赛作品 - 8个字节。
哈希值 - 4个字节。
对象开销 - 8个字节。
对象填充 - 4个字节。
即。对于每个Integer
对象,您需要56个字节来存储HashSet
。如果典型的HashMap
加载因子为0.75,则应为HashMap
数组引用添加另外10个或多个字节。对于每Integer
66个字节,您只能以25 GB存储大约400,000,000个这样的对象,而不考虑应用程序的其余任何任何其他开销。每个任务不到2,000个对象......
编辑3:
最好存储排序的 int[]
数组而不是HashSet
数组。对于任何任意整数,该数组在对数时间内是可搜索的,并且将每个数字的内存消耗最小化为4个字节。考虑到内存I / O,它也可以与HashSet
实现一样快(或更快)。
答案 1 :(得分:1)
如果你想要一个更节省内存的解决方案,我会使用TIntHashSet或排序int[]
。在这种情况下,您会在OutOfMemoryError之前获得Full GC。这些不是问题的原因,而是症状。问题的原因是您使用的内存太多而不允许作为最大堆。
另一种解决方案是随时创建任务,而不是提前创建所有任务。您可以通过将任务分解为NTHREAD任务来完成此操作。您似乎正在尝试保留所有解决方案。如果是这样,这将不会有太大帮助。相反,你需要找到一种减少消费的方法。
根据您的数字分布,BitSet可能更有效。这在一个范围内每个整数使用1位。例如说你的范围是0 - 20,000,这将只使用2.5 KB。
答案 2 :(得分:1)
现在做了一点研究之后,我发现这个内存异常 GC忙于清除内存时发生,此时它停止 整个世界都要执行任何事情。
不 - 不是真的。发生内存异常是因为您使用的内存多于分配给程序的内存。由于GC的某些行为,很少会出现内存异常。如果您将GC配置得不好,就会发生 。
您是否尝试使用较大的Xmx
值运行?为什么不用Hashtable
作为地点?
答案 3 :(得分:1)
答案 4 :(得分:0)
您可能需要增加堆的大小。请查看-Xmx JVM设置。