Java.lang.OutOfMemoryError: 无法创建新的本地线程

163

在8GB RAM VM中,当线程数达到32k时(ps -eLF| grep -c java),我们遇到了"java.lang.OutOfMemoryError : unable to create new native Thread"错误。

然而,"top"和"free -m"显示有50%的可用内存。JDK是64位的,尝试过HotSpot和JRockit。服务器运行Linux 2.6.18。

我们还尝试了OS堆栈大小(ulimit -s)的调整和最大进程(ulimit -u)限制、limit.conf的增加,但都无济于事。

我们几乎尝试了所有可能的堆大小组合,包括保持较低或较高等。

我们用以下脚本来运行应用程序:

/opt/jrockit-jdk1.6/bin/java -Xms512m -Xmx512m -Xss128k -jar JavaNatSimulator.jar /opt/tools/jnatclients/natSimulator.properties

我们尝试编辑 /etc/security/limits.conf 和 ulimit,但问题仍然存在。

[root@jboss02 ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 72192
max locked memory       (kbytes, -l) 32
max memory size         (kbytes, -m) unlimited
open files                      (-n) 65535
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 72192
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

13
操作系统对于能够创建的线程数量是有限制的。为什么你要创建超过32k个线程?你的系统很可能没有成千上万个处理器核心,创建如此多的线程是没有用的。相反,应该使用线程池(ExecutorService)来管理线程。 - Jesper
谢谢回复。我们正在使用一个开源库并尝试进行负载测试。任何该开源库都会创建很多线程。但我不明白的是,当“top”显示50%的空闲内存时,为什么会出现OutOfMemory错误。 - Deepak Tewani
我们在ICE4j库中使用的开源库。 - Deepak Tewani
17
OutOfMemoryError并不一定意味着堆空间或"通用"内存已耗尽。在这种情况下,明显是由于操作系统没有足够资源来分配额外的线程导致失败。有50%的空闲内存与这种特定的故障无关。 - Andrzej Doyle
2
创建新线程需要哪些其他资源?我们的印象是,如果增加RAM,则可以创建更多的线程。请指导我们。 - Deepak Tewani
显示剩余3条评论
15个回答

99

尽管异常名称强烈暗示这是一个内存问题,但实际上这是一个操作系统资源问题。您的本地线程数量不足,即操作系统允许您的JVM使用的线程数量。

这是一个不常见的问题,因为您很少需要那么多线程。您是否有大量无条件的线程生成,而这些线程应该但没有完成?

如果可能的话,您可以考虑重写成使用Callable/Runnable,在Executor的控制下运行。有许多标准执行程序具有各种行为,您的代码可以轻松控制。

(线程数受限的原因有很多,但它们因操作系统而异)


1
可能吧,但我认为这并没有什么帮助。如果在负载测试时资源不足,您需要能够控制应用程序中发生的情况。为什么会同时有32000个线程处于活动状态? - Thorbjørn Ravn Andersen
我们不能使用Web服务器,因为我们正在创建的是与其他客户端通信的客户端。两个客户端都可能在对称、非对称NAT后面。为了实现它们之间的连接,我们使用ice4j库。我不明白的是,如果服务器还剩下50%的内存,那么为什么JVM无法创建更多的线程。 - Deepak Tewani
10
应该将OutOfMemory异常命名为OutOfResources。操作系统无法提供所需的资源。(结果发现我不了解ice4j) - Thorbjørn Ravn Andersen
让我们在聊天中继续这个讨论 - Deepak Tewani
1
@DeepakTewani - 或许运行两个或更多副本的程序可以达到你想要的效果?不过,我认为如果你在系统上运行线程较少且仍然正常工作,很可能你的程序已经通过了负载测试。 :-) - T.E.D.
显示剩余8条评论

16

在负载测试期间,我遇到了相同的问题,原因是JVM无法进一步创建新的Java线程。以下是JVM源代码

if (native_thread->osthread() == NULL) {    
// No one should hold a reference to the 'native_thread'.    
    delete native_thread;   
if (JvmtiExport::should_post_resource_exhausted()) {      
    JvmtiExport::post_resource_exhausted(        
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | 
        JVMTI_RESOURCE_EXHAUSTED_THREADS, 
        "unable to create new native thread");    
    } THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(), "unable to create new native thread");  
} Thread::start(native_thread);`

根本原因:当JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR(资源耗尽,意味着内存耗尽)或JVMTI_RESOURCE_EXHAUSTED_THREADS(线程耗尽)时,JVM会抛出此异常。

在我的情况下,Jboss正在创建过多的线程来处理请求,但所有线程都被阻塞了。由于这个原因,JVM已经用完了线程以及内存(每个线程占用内存,由于每个线程都被阻塞,因此不释放内存)。

分析了Java线程转储,发现近61K个线程都被我们的某个方法阻塞,导致了这个问题。以下是线程转储的部分内容:

"SimpleAsyncTaskExecutor-16562" #38070 prio=5 os_prio=0 tid=0x00007f9985440000 nid=0x2ca6 waiting for monitor entry [0x00007f9d58c2d000]
   java.lang.Thread.State: BLOCKED (on object monitor)

这个方法是如何阻塞的?它从未返回过吗? - Thorbjørn Ravn Andersen
方法可以以多种方式阻塞:等待某些共享的同步对象(在这种情况下),等待缓慢的网络调用超时,或者仅等待某些缓慢的操作系统服务。 - MichaelRom

11
如果jvm是通过systemd启动的,某些Linux操作系统中可能存在每个进程的最大任务数限制(实际上指的是线程)。
您可以通过运行"service status"检查此限制是否存在。如果有,您可以通过编辑/ etc / systemd / system.conf,并添加配置项:DefaultTasksMax = infinity来删除它。

2
进一步说,如果您通过SSH登录到受systemd感染的服务器,然后从CLI启动Java进程,您也可能会遇到sshd服务强加的愚蠢任务限制。 - sayap
谢谢!这是解决线程问题的其中一种方法。还有一个叫做TasksMax的选项(用于更隔离的设置),适用于[Service]下的服务/systemd,它们是: TasksMax=(value)。 在SUSE中,默认值为512(对于生产使用非常有限)。更多信息请参见 https://www.suse.com/support/kb/doc/?id=000015901 执行systemctl daemon-reload命令,并检查systemctl show --property DefaultTasksMax (全局)或 systemctl status ${serviceName}|grep -e Tasks (for TasksMax)以进行确认。 还有其他引起“无法创建新本机线程”的问题,例如ulimit设置。 - Ivan Herlambang

9
很可能是您的操作系统不允许您创建尝试创建的线程数量,或者您正在遇到JVM中的某些限制。特别是如果是32k这样的一个圆数,某种限制很可能是罪魁祸首。
您确定您真正需要32k个线程吗?大多数现代语言都支持可重用线程池 - 我相信Java也有类似的支持(如用户Jesper所提到的ExecutorService)。也许您可以从这样的池中请求线程,而不是手动创建新线程。

1
谢谢回复。我们正在使用开源库ICE4j并尝试进行负载测试。当我们知道服务器上还剩50%的内存时,我们不能增加操作系统中的线程限制吗? - Deepak Tewani
1
我们正在创建11K个客户端,使用32K个线程在UDP套接字上读写数据。其中10K个线程是保持活动状态的线程,用于保持套接字开放。 - Deepak Tewani

7
我建议您也查看线程堆栈大小并确定是否需要创建更多线程。对于JRockit 1.5/1.6,64位VM在Linux操作系统上的默认线程堆栈大小为1 MB。32K个线程将需要大量物理和虚拟内存才能满足此要求。 尝试将��栈大小减小到512 KB作为起点,看看它是否有助于创建更多应用程序线程。我还建议探索横向扩展,例如将应用程序处理分割成更多物理或虚拟机器。 使用64位VM时,真正的限制取决于操作系统可用的物理和虚拟内存以及操作系统调整参数,如ulimitc。我还建议参考以下文章: OutOfMemoryError:无法创建新本机线程-问题解析

5
由于在bash中使用top命令时无法显示幽灵进程,导致JVM无法生成更多的线程,我遇到了同样的问题。对我来说,在shell中使用jps列出所有的Java进程,并使用每个幽灵进程的"kill -9 pid" bash命令单独杀死它们可以解决这个问题。在某些情况下,这可能会有所帮助。请参考jps文档

5

我在一个CentOS/Red Hat机器上遇到了同样的问题。您已经达到了线程限制,可能是用户、进程或总体限制。

在我的情况下,用户可以拥有的线程数量受到限制。可以使用以下命令检查,其中一行显示最大用户进程数:

ulimit -a

您可以使用此命令查看运行的线程数:

$ ps -elfT | wc -l

要获取您的进程正在运行的线程数(可以使用top或ps aux获取进程pid):

$ ps -p <PROCESS_PID> -lfT | wc -l

/proc/sys/kernel/threads-max文件提供了系统范围内的线程数限制。root用户可以更改该值。

要更改限制(在本例中为4096个线程):

$ ulimit -u 4096

您可以在此处找到有关Red Hat/centOs的更多信息http://www.mastertheboss.com/jboss-server/jboss-monitoring/how-to-solve-javalangoutofmemoryerror-unable-to-create-new-native-thread

我没想到线程也是进程。 - zhuguowei

4

这个错误可能由以下两个原因引起:

  • 内存中没有足够的空间来容纳新的线程。

  • 线程数超过操作系统的限制。

我怀疑线程数已经超过了java进程的限制。

因此,可能是由于内存问题导致该问题出现。需要考虑的一点是:

线程不是在JVM堆内创建的,而是在JVM堆外创建的。所以,如果在JVM堆分配后RAM中剩余的空间较少,则应用程序将遇到“java.lang.OutOfMemoryError:unable to create new native thread”。

可能的解决方案是减少堆内存或增加总内存大小。


2

查找创建线程的进程,请尝试:

ps huH

我通常将输出重定向到文件并离线分析该文件(检查每个进程的线程数是否符合预期)


2
每当JVM请求从操作系统中获取新的线程时,你有机会面对 java.lang.OutOfMemoryError: Unable to create new native thread。当底层操作系统无法分配新的本地线程时,将抛出此 OutOfMemoryError。本地线程的确切限制非常依赖于平台,因此建议通过运行类似于下面链接示例的测试来找出这些限制。但是,通常导致 java.lang.OutOfMemoryError: Unable to create new native thread 的情况经历以下阶段:
  1. 应用程序在JVM内部运行并请求一个新的Java线程
  2. JVM本地代码代理创建新的本地线程的请求到操作系统
  3. 操作系统尝试创建需要为线程分配内存的新的本地线程
  4. 操作系统将拒绝本地内存分配,要么是因为32位Java进程大小已经耗尽了其内存地址空间(例如达到了2-4 GB进程大小限制),要么是因为操作系统的虚拟内存已经完全耗尽
  5. 抛出 java.lang.OutOfMemoryError: Unable to create new native thread 错误。
参考: https://plumbr.eu/outofmemoryerror/unable-to-create-new-native-thread

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