【问题标题】:Multithread - OutOfMemory多线程 - OutOfMemory
【发布时间】:2013-09-08 15:25:52
【问题描述】:

我正在使用一个有 5 个活动线程的 ThreadPoolExecutor,任务数量是 20,000 个。
队列几乎立即被 Runnable 任务的实例填满 (pool.execute(new WorkingThreadTask()))。

每个WorkingThreadTask 都有一个HashMap

Map<Integer, HashMap<Integer, String>> themap ;

每个地图最多可以有 2000 个项目,每个子地图有 5 个项目。还有一个共享的BlockingQueue

当进程运行时,我的内存不足。我正在运行:(32bit -Xms1024m -Xmx1024m)

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

更新:

运行分析器并检查内存后,最大的命中是:

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

我不知道它是从哪里调用或使用的。

Name    Instance count  Size (bytes)
byte[ ] 2519560 918117496
oracle.jdbc.ttc7.TTCItem    2515402 120739296
char[ ] 357882  15549280
java.lang.String    9677    232248
int[ ]  2128    110976
short[ ]    2097    150024
java.lang.Class 1537    635704
java.util.concurrent.locks.ReentrantLock$NonfairSync    1489    35736
java.util.Hashtable$Entry   1417    34008
java.util.concurrent.ConcurrentHashMap$HashEntry[ ] 1376    22312
java.util.concurrent.ConcurrentHashMap$Segment  1376    44032
java.lang.Object[ ] 1279    60216
java.util.TreeMap$Entry 828 26496
oracle.jdbc.dbaccess.DBItem[ ]  802 10419712
oracle.jdbc.ttc7.v8TTIoac   732 52704

【问题讨论】:

  • 尝试分析内存,看看是什么在吞噬它。 HashMaps 应该得到 GC'd,但前提是之后没有任何东西保留对它们的引用。
  • 如果hashmap不共享怎么保留引用?
  • 我不知道你的代码。例如,可以将引用与任务的结果一起传递到其他地方。但它可以是完全不同的东西,内存配置文件会告诉你程序用什么填充了内存。
  • 20,000 个可运行项 x 2,000 个地图项 x 5 个子图 = 200,000,000 个对象...
  • Igor,200,000,000 bu 每个胎面仅处理 2000*5,完成后应该清理 hashmap

标签: java multithreading memory-management hashmap


【解决方案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 被清理了对吗?

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

如果这不起作用,则可以使用分析器来帮助您确定内存所在的位置。

编辑:

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

【讨论】:

  • 即使 ArrayBlockingQueue 大小为 5 也会出现同样的问题
  • 我认为原因是已完成任务中的 hashmap 没有被清除。
  • @markiz 要做的一件事是积极呼吁清除工作 HashMap 和其他收藏。虽然它们最终应该被 GC 收割,但给 GC 一些帮助可能会解决您的问题。否则,探查器就是要走的路。
  • 你在做任何序列化@markiz? byte[] 很有趣。您要关闭数据库连接吗?
  • 我正在重用连接,这是我发现使用后没有关闭 RecordSet 和 PreparedStatement 的问题之一。连接被重用,但记录集和 PreparedStatement 未被重用,而是在不关闭前一个的情况下重新创建。
【解决方案2】:

HashMap 在内存使用方面带来了相当多的开销......它每个条目至少携带大约 36 个字节,加上键/值本身的大小 - 每个将至少 32 个字节(我认为这是关于 32 位 sun 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 在 executor 的队列中可用时,您可以创建和添加另一个任务。

【讨论】:

  • 但是为什么呢?每个任务处理 200 * 5 个项目。 5个线程同时运行。任务完成后,所有数据都会被清理。不是吗?
  • 这取决于有多少任务排队等待执行。此外,您可能还需要处理 Future 对象。
  • 如果任务在等待,它的HM是空的。只有在运行时才会被填满
【解决方案3】:

您可以通过在正在处理的内容之前不添加太多任务来提高效率。尝试检查队列并仅在条目少于 1000 个时才添加。

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

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

【讨论】:

  • 对于您的第一点,我尝试了 Gray 的建议,但没有成功。如果这有帮助,我会再做一个结构......关于内存,它将在 64 位机器上运行,但现在我正在 32 上工作和测试。
  • 我在 4GB 或更少的机器上运行大量的中型到大型数据处理。当然,64 位总是一个好主意,但在我看来,Peter 被他的 32+gb 内核宠坏了。 ;-)
  • @Gray 我以前有 256 GB 用于工作,但现在我用 32 GB 凑合了。我有一个 480 GB PCI SSD,所以内存不足对性能有很大影响。顺便说一句,32 GB 游戏内存 + 480 GB PCI SSD
  • 哦,是的,SSD 是第一个要求,但 32gb 对我来说仍然是很多内存。 :-) 我做笔记本电脑已经有一段时间了,他们通常只是没有插槽空间或芯片密度来做这件事。哦,我很便宜。再说一次,我没有像你那样做高性能的 fu。干杯。
  • @Gray 您可以花 210 英镑购买 32 GB。我有一台配备 8 GB 和 256GB SSD 的超极本,足以满足我在上面的工作。我的下一台机器将有 64 GB。顺便说一句,当我为儿子 8 岁生日买了一台电脑时,我买了 8 GB 的电脑,因为 4 GB 只能节省 20 英镑。
【解决方案4】:

从大的byte[]s,我怀疑与 IO 相关的问题(除非您正在处理视频/音频或其他东西)。

看点:

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

UPDATE:好的,所以您正在使用游标从 DB 中读取。现在您需要确保从光标读取的内容仅在您完成内容时才会进行(也称为“传播负载”)。为此,请使用这样的线程池:

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

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

【讨论】:

  • 是的,我正在阅读大量数据库数据。我正在使用游标 ResultSet.next
猜你喜欢
  • 2010-12-20
  • 2015-01-27
  • 1970-01-01
  • 2012-05-29
  • 1970-01-01
  • 2013-07-19
  • 1970-01-01
  • 2011-03-30
  • 1970-01-01
相关资源
最近更新 更多