Java 多线程-OutOfMemory

Java 多线程-OutOfMemory,java,multithreading,memory-management,hashmap,Java,Multithreading,Memory Management,Hashmap,我使用的ThreadPoolExecutor有5个活动线程,任务数量非常庞大。 队列几乎立即被可运行任务的实例填满(pool.execute(new WorkingThreadTask())) 每个工作读取任务都有一个哈希映射: Map<Integer, HashMap<Integer, String>> themap ; 我不知道它是从哪里来的 Name Instance count Size (bytes) byte[ ] 2519560 918117496

我使用的
ThreadPoolExecutor
有5个活动线程,任务数量非常庞大。
队列几乎立即被可运行任务的实例填满(
pool.execute(new WorkingThreadTask())

每个
工作读取任务
都有一个
哈希映射

Map<Integer, HashMap<Integer, String>> themap ;
我不知道它是从哪里来的

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

我不确定内部映射是否正确,但我怀疑问题在于您正在创建大量的任务,这些任务占用了内存。您应该使用有界任务队列并限制作业生产者

看看我的答案:

总之,您应该创建自己的有界队列,然后使用
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);
           }
    }
});
final BlockingQueue队列=
新的ArrayBlockingQueue(100);
线程池执行器线程池=
新的ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.millizes,queue);
//如果队列已满,我们需要我们的RejectedExecutionHandler来阻止
setRejectedExecutionHandler(新的RejectedExecutionHandler(){
@凌驾
public void拒绝执行(工作读取任务任务,
线程池执行器(执行器){
试一试{
//这将阻止制作人,直到队列中有空间为止
executor.getQueue().put(任务);
}捕捉(中断异常e){
抛出新的RejectedExecutionException(
“意外中断异常”,e);
}
}
});
编辑:

我想hashmap中没有韭菜。。。当线程完成时,hashmap是否被清理

在任务完成时,您可以考虑在工作<代码> HashMap < /C>和其他集合上积极调用<代码> CULL()/代码>。虽然GC最终应该会获得这些信息,但如果您的内存有限,给GC一些帮助可能会解决您的问题

如果这不起作用,可以使用探查器来帮助您确定内存存放的位置

编辑:


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

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在执行者队列中可用时,您可以创建并添加另一个任务。

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

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


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

从大的
字节[]
s,我会怀疑IO相关问题(除非您正在处理视频/音频或其他内容)

需要注意的事项:

  • DB:你想一次读大量的东西吗?你可以 e、 g.使用光标不要这样做
  • 文件/网络:您是否试图同时从文件/网络中读取大量内容?您应该将负载“传播”到任何正在读取的对象,并调节读取速率
更新:好的,您正在使用光标从数据库中读取数据。现在,您需要确保从光标读取的内容只在您完成内容时进行(也称为“传播负载”)。为此,请使用如下线程池:

 BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(queueSize);
 ThreadPoolExecutor tpe = new ThreadPoolExecutor(
                    threadNum,
                    threadNum,
                    1000,
                    TimeUnit.HOURS,
                    queue,
                    new ThreadPoolExecutor.CallerRunsPolicy());
BlockingQueue=newlinkedblockingqueue(queueSize);
ThreadPoolExecutor tpe=新的ThreadPoolExecutor(
threadNum,
threadNum,
1000,
时间单位:小时,
队列
新的ThreadPoolExecutor.CallerRunPolicy());

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

尝试分析内存,看看是什么在消耗它。
HashMap
s应该得到GC'd,但前提是以后没有任何东西保留对它们的引用。如果HashMap没有被共享,它怎么能保留引用?我不知道你的代码。例如,可以使用任务的结果将引用传递到其他地方。但它可能是完全不同的,内存配置文件会告诉您程序在内存中填充了什么。20000个可运行项x 2000个映射项x 5个子映射=200000000个对象…Igor,200000000 bu每个踏板只处理2000*5个,在它找到hashmap后应该清理,但为什么?每个任务处理200*5个项目。5个线程同时运行。任务完成后,它的所有数据都将被清除。不是吗?这取决于排队等待执行的任务数量。此外,您可能还需要处理未来的对象。如果任务正在等待,则HM为空。它只有在运行“名称问题”时才被填充,即使ArrayBlockingQueue
 BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(queueSize);
 ThreadPoolExecutor tpe = new ThreadPoolExecutor(
                    threadNum,
                    threadNum,
                    1000,
                    TimeUnit.HOURS,
                    queue,
                    new ThreadPoolExecutor.CallerRunsPolicy());