内存不足:使用hashset进行multithreading处理

我已经实现了一个java程序。 这基本上是一个具有固定线程数的multithreading服务。 每个线程一次执行一个任务,创建一个hashSet,hashset的大小可以在单个hashset中从10到20,000个项目变化。 在每个线程结束时,使用synchronized将结果添加到共享集合List。

问题发生在某些时候我开始出现内存exception。 在进行了一些研究之后,我发现当GC忙于清除内存时会发生这种内存exception,此时它会阻止整个世界执行任何操作。

请给我关于如何处理如此大量数据的建议。 Hashset是否是一个正确的数据结构? 如何处理内存exception,我的意思是一种方法是使用System.GC(),这又不好,因为它会减慢整个过程。 或者,在将其添加到共享集合列表后,是否可以处理“HashSet hsN”?

请让我知道你的想法并指导我,无论我哪里出错。 这项服务将处理大量的数据处理。

谢谢

//business object - to save the result of thread execution public class Location{ integer taskIndex; HashSet hsN; } //task to be performed by each thread public class MyTask implements Runnable { MyTask(long task) { this.task = task; } @Override public void run() { HashSet 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 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?

我可以看到几个问题:

  • 您在MyTask对象上进行同步,该对象是为每次执行单独创建的。 您应该在共享对象上进行同步,最好是您正在修改的对象,即locations对象。

  • 216,000次运行,再乘以10,000个返回的对象,每个Integer对象最少12个字节,大约24 GB的内存。 您是否在计算机上拥有那么多物理内存,更不用说JVM了?

    32位JVM的堆大小限制小于2 GB。 另一方面,在64位JVM上, Integer对象大约需要16个字节,这会将内存需求提高到30 GB以上。

    有了这些数字,你得到一个OutOfMemoryError不足为奇了……

    PS:如果你确实拥有那么多可用的物理内存并且你仍然认为你正在做正确的事情,你可能想看看调整JVM堆大小 。

编辑:

即使JVM可以使用25GB的内存,它仍然可以推动它:

  • 每个Integer对象在现代64位JVM上需要16个字节。

  • 无论您使用哪种List实现,您还需要一个指向它的8字节引用

  • 如果使用链表实现,则每个条目的列表条目对象的开销也至少为24字节。

最好你可能希望以25GB存储大约1,000,000,000个Integer对象 – 如果你使用的是链接列表的一半。 这意味着每个任务平均不会产生超过5,000个(分别为2,500个)对象而不会导致错误。

我不确定你的确切要求,但是你考虑过返回一个更紧凑的物体吗? 例如,从每个HashSet生成的int[]数组只保留每个结果最少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实现一样快(或更快)。

如果你想要一个更有效的内存解决方案,我会使用TIntHashSet或一个有序的int[] 。 在这种情况下,您会在OutOfMemoryError之前获得Full GC。 这些不是问题的原因,而是症状。 问题的原因是您使用的内存太多,因为您允许的最大堆数量。

另一种解决方案是在您开始时创建任务,而不是提前创建所有任务。 您可以通过将任务分解为NTHREAD任务来完成此操作。 您似乎正在尝试保留所有解决方案。 如果是这样,这将无济于事。 相反,你需要找到一种减少消费的方法。

根据您的数字分布,BitSet可能更有效。 这在一个范围内每个整数使用1位。 例如,你的范围是0 – 20,000,这将只使用2.5 KB。

  • 如果要在内存中保留216000 * 10000个整数,则需要大量内存。
  • 您可以尝试在系统中允许的最大Xmx设置,并查看在内存不足之前可以存储的对象数。
  • 目前尚不清楚为什么要存储这么multithreading的处理结果,下一步是什么? 如果您确实需要存储大量数据,则可能需要使用数据库。

在进行了一些研究之后,我发现当GC忙于清除内存时会发生这种内存exception,此时它会阻止整个世界执行任何操作。

不 – 不是真的。 发生内存exception是因为您使用的内存多于分配给程序的内存。 由于GC的某些行为,很少会出现内存exception。 如果您将GC配置得很差, 就会发生这种情况。

您是否尝试使用更大的Xmx值运行? 为什么不直接使用Hashtable作为位置?

您可能需要增加堆的大小。 请查看-Xmx JVM设置。