多线程 - OutOfMemory

时间:2013-09-08 15:25:53

标签: java multithreading memory-management hashmap

我使用的ThreadPoolExecutor有5个活动线程,任务数量是20,000个 pool.execute(new WorkingThreadTask()))。

每个Runnable都有一个WorkingThreadTask

HashMap

每张地图最多可包含2000个项目,每个子地图有5个项目。还有一个共享Map<Integer, HashMap<Integer, String>> themap ;

当进程正在运行时,我的内存不足。我正在使用:BlockingQueue

我该如何处理这个问题?我不认为我在hashmap中有泄漏......当线程完成时,hashmap被清理干净了吗?

更新

运行探查器并检查内存后,最大的打击是:

(32bit -Xms1024m -Xmx1024m)

我不知道它被叫或使用的地方。

byte[] 2,516,024 hits, 918 MB  

4 个答案:

答案 0 :(得分:1)

我不确定内部地图,但我怀疑问题是你正在创建大量填充内存的任务。您应该使用有界任务队列并限制作业生成器。

在这里查看我的答案:Process Large File for HTTP Calls in Java

总结一下,您应该创建自己的有界队列,然后使用RejectedExecutionHandler来阻止生成器,直到队列中有空间为止。类似的东西:

final BlockingQueue<WorkingThreadTask> queue =
    new ArrayBlockingQueue<WorkingThreadTask>(100);
ThreadPoolExecutor threadPool =
    new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, queue);
// we need our RejectedExecutionHandler to block if the queue is full
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
       @Override
       public void rejectedExecution(WorkingThreadTask task,
             ThreadPoolExecutor executor) {
           try {
                // this will block the producer until there's room in the queue
                executor.getQueue().put(task);
           } catch (InterruptedException e) {
                throw new RejectedExecutionException(
                   "Unexpected InterruptedException", e);
           }
    }
});

修改

  

我不认为我在hashmap中有韭菜...当线程完成时,hashmap被清理好了吗?

在任务完成时,您可能会考虑在工作clear()和其他集合上积极调用HashMap。虽然它们最终应该由GC获得,但如果你的内存有限,给GC提供一些帮助可能会解决你的问题。

如果这不起作用,可以使用分析器来帮助您识别内存的保存位置。

修改

在查看探查器输出后,byte[]很有趣。通常,这表示某种序列化或其他IO。您可能还在数据库中存储blob。然而,oracle.jdbc.ttc7.TTCItem 非常非常有趣。这向我表明你没有在某处关闭数据库连接。确保使用正确的try / finally块来关闭连接。

答案 1 :(得分:0)

HashMap在内存使用方面带来了相当多的开销.....每个条目最少占用36个字节,加上键/值本身的大小 - 每个至少32个字节(我认为这是关于32位太阳JVM的典型值....做一些快速数学:

20,000 tasks, each with map with 2000 entry hashmap. The value in the map is another map with 5 entries.
->  5-entry map is 1* Map + 5* Map.Object entries + 5*keys + 5*values = 16 objects at 32 bytes => 512 bytes per sub-map.
->  2000 entry map is 1* Map, 2000*Map.Object + 2000 keys + 2000 submaps (each is 512 bytes) => 2000*(512+32+32) + 32 => 1.1MB
->  20,000 tasks, each of 1.1MB -> 23GB

所以,你的整体足迹是23GB。

逻辑解决方案是限制提供ExecutorService的阻塞队列的深度,并且只创建足够的子任务以使其保持忙碌.....在队列中设置约64个条目的限制,然后你永远不会一次实例化超过64 + 5个任务。当wpace在执行程序的队列中可用时,您可以创建并添加另一个任务。

答案 2 :(得分:0)

您可以在处理之前不添加如此多的任务来提高效率。尝试检查队列,只有在少于1000个条目时才添加它。

您还可以提高数据结构的效率。带有整数键的Map通常可以简化为某种数组。

最后,目前1 GB并不是那么多。我的手机有2 GB。如果您要处理大量数据,我建议使用32-64 GB内存和64位JVM。

答案 3 :(得分:0)

从大byte[]开始,我怀疑与IO有关的问题(除非您正在处理视频/音频或其他事情)。

要看的事情:

  • DB:你是想一次读大量的东西吗?您可以 例如使用游标不这样做
  • 文件/网络:您是否尝试一次从文件/网络中读取大量内容?您应该“将负载”传播到正在读取的内容并调节读取速率。

更新:好的,所以您正在使用游标从数据库中读取数据。现在你需要确保光标的读数只在你完成的东西(也称为“传播负载”)时进行。为此,请使用如下的线程池:

 BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(queueSize);
 ThreadPoolExecutor tpe = new ThreadPoolExecutor(
                    threadNum,
                    threadNum,
                    1000,
                    TimeUnit.HOURS,
                    queue,
                    new ThreadPoolExecutor.CallerRunsPolicy());

现在,当您从从DB读取的代码发布到此服务时,它将在队列已满时阻塞(调用线程用于运行任务,因此阻止)。