内存不足:使用hashset进行多线程处理

时间:2012-02-25 17:38:59

标签: java multithreading

我已经实现了一个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?

5 个答案:

答案 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)

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

答案 4 :(得分:0)

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