创建少于100个线程时出现Java的OutOfMemoryError问题

12

由于这个错误,我已经阅读、测试并且苦思冥想了一整天。我的一个名叫Listener的Java类中有下面这段代码:

ExecutorService executor = Executors.newFixedThreadPool(NTHREADS);
boolean listening = true;
int count = 0;
while (listening) {
    Runnable worker;
    try {
        worker = new ServerThread(serverSocket.accept()); // this is line 254
        executor.execute(worker);
        count++;
        logger.info("{} threads started", count);
    } catch (Exception e1){
        //...
    }
}

我一直在调整JVM设置-Xmx(从1到15G不等)和-Xss(从104k到512M不等)。服务器有24GB的RAM,但还必须运行支持程序的数据库。

创建了2-20个线程后(程序中其他地方还有几十个线程),我会收到错误消息。

Exception in thread "Thread-0" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:657)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:943)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1325)
at xxx.Listener.run(Listener.java:254)

$java -version的输出结果如下:

java version "1.6.0_24"
OpenJDK Runtime Environment (IcedTea6 1.11.1) (fedora-65.1.11.1.fc16-x86_64)
OpenJDK 64-Bit Server VM (build 20.0-b12, mixed mode)
当这种情况发生时,系统上始终有大量的空闲内存,并且其他程序继续正常执行。是什么导致Java认为没有足够的内存来创建新线程?
更新: 也许这比我想象的要大 - 我在使用^C时(仅此一次)遇到了此错误:
OpenJDK 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated

当我试图关闭客户端时(也是用Java编写,并在同一服务器上运行,它是一个单线程,读取文件并通过套接字将其发送到服务器),情况也是如此,因此绝对存在限制超出JVM导致干扰另一个,但如果我仍然有免费的内存并且完全没有使用交换?服务器-Xmx1G -Xss104k 客户端-Xmx10M

更新2:放弃perl Forks::Super库,从bash运行客户端,在服务器崩溃并出现OOME之前可以达到34个线程,因此运行多个客户端肯定会影响服务器,但同时我仍然能够同时运行超过34个(如果将客户端计算在内则为68)java线程。哪些系统资源阻止了更多线程的创建(即我应该在哪里查找占用资源)?当所有内容(客户端、服务器、GC...)同时耗尽内存时,top显示我的CPU和内存使用情况:

Cpu(s):  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  24681040k total,  1029420k used, 23651620k free,    30648k buffers
Swap: 26836988k total,        0k used, 26836988k free,   453620k cached

更新3:下面的 hs_error 日志是否表明我的 Java 不是 64 位的?

# There is insufficient memory for the Java Runtime Environment to continue.
# Cannot create GC thread. Out of system resources.
# Possible reasons:
#   The system is out of physical RAM or swap space
#   In 32 bit mode, the process size limit was hit
# Possible solutions:
#   Reduce memory load on the system
#   Increase physical memory or swap space
#   Check if swap backing store is full
#   Use 64 bit Java on a 64 bit OS
#   Decrease Java heap size (-Xmx/-Xms)
#   Decrease number of Java threads
#   Decrease Java thread stack sizes (-Xss)
#   Set larger code cache with -XX:ReservedCodeCacheSize=
# This output file may be truncated or incomplete.
#
# JRE version: 6.0_24-b24
# Java VM: OpenJDK 64-Bit Server VM (20.0-b12 mixed mode linux-amd64 compressed oops)
# Derivative: IcedTea6 1.11.1
# Distribution: Fedora release 16 (Verne), package fedora-65.1.11.1.fc16-x86_64

ServerThread是java.lang.Thread的实例吗?如果是,它应该使用Thread.start()启动,而线程池则无用。如果不是,那么你将如何使用简单的Runnable来服务套接字连接?这很棘手,因为在线程池控制下的任务不允许等待输入,否则可能会发生线程饥饿(一种死锁)的情况。 - Alexei Kaigorodov
1
ulimit -u 命令会返回最大用户进程数,也许你的限制就在这里。 - alain.janinm
1
ulimit -u 返回1024 - 这可能是问题所在。怎样更改这个设置呢? - kaz
1
尝试这个:在/etc/security/limits.conf中设置user soft nproc [your_val]user hard nproc [your_val]。如果不够的话,您可能需要添加一些其他配置,请参阅此链接http://directory.fedoraproject.org/wiki/Performance_Tuning。 - alain.janinm
2
对于未来阅读此内容的人,请注意在 Fedora 和 CentOS 中有一个错误报告,其中解释了编辑 /etc/security/limits.conf 的限制。链接 @alain.janinm:更改 nproc 值的此解决方案确实解决了我的问题。谢谢。 - kaz
显示剩余5条评论
4个回答

11

您可以通过最大用户进程数来限制,要了解您的限制,请使用:

ulimit -u

要改变限制:

/etc/security/limits.conf中设置:

user soft nproc [your_val] 
user hard nproc [your_val]

如果这些配置不足够的话,你可能需要添加其他配置,可以参考此链接

注意:原帖作者在fedora和centos中找到了此bug报告,该报告解释了编辑/etc/security/limits.conf的限制。


如果那个通配符(*)是我使用的Centos 6版本头痛的原因,那我就完了。通配符在野外受到惩罚。不仅仅是我,而且专业支持人员显然也不知道这一点。 - nir
我不得不更改 /etc/security/limits.d/90-nproc.conf,这是一个安全文件,也可以限制线程数。 - Jose1755

3

希望它可以这么简单,但是通过使用“-Xmx1G -Xss104k”,我能够创建6个线程(总计不到40个在程序中明确创建的线程)。104k是我的系统上最小的-Xss值,而1G远远不足以在生产环境中使用。同时,在内存耗尽时,系统仍有大量(超过8G)可用空间。 - kaz
104k是一个奇怪的值 :) 这个文档讲述了64k的最小值 http://www.oracle.com/technetwork/java/hotspotfaq-138619.html#threads_oom - alain.janinm
确实很奇怪,但当我使用小于104的堆栈大小启动JVM时,它会抛出此消息并崩溃:指定的堆栈大小太小,请至少指定104k。无法创建Java虚拟机。 - kaz

3

你的新线程不是缺少内存,而是缺少实际线程。系统可能会阻止你:用户创建的线程数量有限制。你可以通过以下方式查询:

cat /proc/sys/kernel/threads-max

请注意,在同一台计算机上可能会受到其他进程的影响,如果它们也创建了许多线程,则可能会产生影响。 您可能会发现这个问题很有用: Linux中每个进程的最大线程数是多少?

“$ cat /proc/sys/kernel/threads-max” 返回 385345 - 在运行我的程序、几个 ssh/bash shells 和 MySQL 的服务器上,我不认为我达到了这个限制。此外,一些特别糟糕的 jvm 设置会导致它在创建比其他线程更少的线程后死亡,这表明这是一个 JVM 问题。 - kaz

1

仅供澄清:

您向Thread提供一个ServerSocket。您是否将数据发送到该套接字?也许您在线程上下文中存储了太多的数据。请查找一种模式,在其中将流数据存储在byte[]中。


不,我向线程提供了一个SocketServerSocket.accept()返回一个Socket对象。是的,我从另一个程序传递数据到它,并读取该数据,处理它,然后断开连接。在测试版本中,线程只接受套接字,等待一段时间,然后关闭它并死亡。套接字像流一样被读取,因此我认为我没有将整个流的内容传递给线程。此外,在客户端进行身份验证之前,它不会发送数据,因此流缓冲区仅有几百个字节。 - kaz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接