无法创建新的本机线程错误 - 但很少有线程在使用

15
我们有一个被广泛部署的应用程序(有几百个工作站在运行它)。在一个站点上(只有一个站点-我们的产品被广泛部署到许多环境中),我们随机地收到以下错误消息:
java.lang.OutOfMemoryError:无法创建新的本机线程 at java.lang.Thread.start0(Native Method) at java.lang.Thread.start(Unknown Source)
操作系统是Windows 7 64位 我们正在运行32位JVM(1.7.0_45)
使用Windows任务管理器,我可以看到该进程有39个本机线程(不是很多),因此我们的应用程序没有线程泄漏...没有其他进程消耗大量线程(Explorer有35个,jvisualvm有24个,iexplore有20个,...我没有确切的数量,但是总体用户可能使用的线程数可能只有300个左右)。
我已经尝试连接JVisualVM,但它无法连接到该进程(可能是由于线程耗尽)。但从JVisualVM可以获取的指标来看,Java线程数量约为22个活动线程和11个守护线程。
堆表现良好-堆大小为500MB,实际使用了250MB。
该进程使用-Xmx512m启动。
我们的进程在任务管理器中显示的内存使用量为597,744K。
工作站有8GB RAM,其中只使用了3.8-4.0GB(我知道,32位进程无法访问所有RAM,但仍然有足够的空间)。
使用VMMap,堆栈大小为49,920KB,已提交2,284K。
该进程显示5358KB可用,而自由列表中最大可分配块的大小为1,024K。
我使用资源监视器,它显示的提交(KB)为630428,工作集(KB)为676,996,可共享(KB)为79,252,私有(KB)为597,744。
我完全不知道这里发生了什么。我已经阅读了很多关于此问题的文章,听起来在一些Linux系统上,每个用户线程有一个限制,可能会导致问题(但这不是Linux,并且其他文章中描述的问题通常需要数千个线程-绝对不是我们这里的情况)。
如果我们的堆确实很大,我可以看到它会占用线程可用的空间,但是500MB似乎是一个非常合理且小的堆(特别是对于拥有8GB RAM的工作站)。
所以我已经尽我所能做的一切 - 有人对这里可能发生的事情有任何额外的指针吗?
编辑1:
我发现了这篇有趣的文章:Eclipse crashes with "Unable to create new native thread" - any ideas? (my settings and info inside)
他们认为堆栈大小可能是问题。
这篇文章:where to find default XSS value for Sun/Oracle JVM? - 给出了一个指向Oracle文档的链接,说明默认的堆栈大小是512KB。因此,如果我的应用程序有大约40个线程,那么我们正在查看20 MB的堆栈。500MB堆。所有这些似乎都在32位Java进程的正常范围内。
所以我能想到的只有两种可能性:
  1. 某些短暂的条件导致大量线程被创建(但这些线程在我们进行诊断之前就被丢弃了)
  2. 内存分段出现问题。有趣的是,每个VMMap中可分配的最大块(1MB)似乎并不多......在另一台正常工作的机器上,最大可分配块为470MB......

那么,有没有关于如何检查内存分段的指针?

编辑2:

@mikhael链接的文章(http://blog.egilh.com/2006/06/2811aspx.html)提供了32位JVM允许的线程数量的粗略计算。

我将假设:

OS进程空间限制:2GB 现代JVM需要250MB(这是一个很大的假设-我只是将链接文章中的数字加倍) 堆栈大小(默认Oracle):512KB 堆:512MB PermGen:(记不清确切数字,但肯定少于100MB,所以就用那个数字吧)

所以我有一个最坏的情况:(2GB - .25GB - .5GB - .1GB)/.005GB = 230个线程

编辑3:

我应该最初包含的信息:应用程序在此问题发生之前可以正常运行很长时间(如24到48小时)。该应用程序进行持续的后台处理,因此空闲时间非常少。不确定是否重要...

编辑4:

更多信息:从另一个失败中查看VMMap,我看到本机堆耗尽。

堆大小为1.2GB,仅提交了59.8MB。

Java运行时可能是问题所在,或者可能存在某些未正确释放的本机资源问题?例如可能是未被释放的内存映射文件?

我们确实使用内存映射文件,因此我将把重点放在这些文件上。

编辑4:

我认为我已经追踪到问题所在,即出现以下异常:

java.lang.OutOfMemoryError
    at java.util.zip.Deflater.init(Native Method)
    at java.util.zip.Deflater.<init>(Unknown Source)
    at java.util.zip.Deflater.<init>(Unknown Source)
    at java.util.zip.DeflaterOutputStream.<init>(Unknown Source)
    at java.util.zip.DeflaterOutputStream.<init>(Unknown Source)
    at ....

在一些非常少的流(我现在有4个例子)中,我们正在进行压缩,发生了上述情况。当它发生时,VMMap会将进程的堆(不是JVM堆,而是实际的本地堆)增加到2GB。一旦发生这种情况,一切都崩溃了。这现在非常可重复(将相同的流运行到deflater中会导致内存飙升)。
那么,我们可能正在寻找JRE的zip库的问题吗?似乎认为这会是问题很疯狂,但我真的很无助。
如果我取相同的流并在另一个系统上运行它(即使运行相同的JRE-32位,Java 7u45),我们不会遇到问题。我已经完全卸载了JRE并重新安装了它,但行为没有任何改变。

你说的“站点”,是指在同一物理位置上有许多相同的机器,它们都显示出这种行为吗? - Thorbjørn Ravn Andersen
你能分享所有的JVM参数吗?Xmx参数对于线程创建没有太大的影响。 - benjamin.d
你确定你的线程计数没问题吗?有没有可能它会快速地超出你的线程计数,以至于你的工具无法检测到它? - Gray
看看这个链接是否有帮助:http://javaeesupportpatterns.blogspot.de/2012/09/outofmemoryerror-unable-to-create-new.html - Nitin Dandriyal
2
你没有线程问题,而是内存使用问题。错误信息只是简单地说明你已经用完了内存,因此我们无法执行你的请求(创建另一个线程)。有时你可以在代码开头放置一个sleep(),然后再附加监视器。这样它就能在内存耗尽之前启动。 - edharned
显示剩余12条评论
2个回答

4

最终解决了这个问题。

我们处理了几个数据流(在该站点的1000万个数据流中有4个),这些数据流最终创建了大量的DeflaterOutputStream对象。我们使用的第三方库在流上调用finish()而不是close()。底层的Deflater终止程序正在清理事物,只要负载不太高,就没有问题。但是超过一个临界点后,我们开始遇到以下问题:

http://jira.pentaho.com/browse/PRD-3211

这导致我们找到了以下内容:

http://bugs.sun.com/view_bug.do?bug_id=4797189

几个小时后,系统最终陷入了一个无法摆脱的困境,并且在需要时无法创建本机线程。

解决方法是让第三方库关闭DeflaterOutputStream。

因此,这绝对是本地资源泄漏。如果其他人也遇到类似的问题,VMMap工具对于最终跟踪导致问题的数据流非常有用。


0

我怀疑,虽然很难证明,但您可能遇到了32位内存分配问题。

线程被分配本地内存,而不是堆内存,后者必须是连续的才能运行。虽然我确信WOW64允许32位进程在4GB以上的区域运行,但如果介于空间被使用,则不确定为新线程分配本地内存是否超过4GB限制。

因此,您的应用程序和堆位于较低的内存中,其他进程正在占用中间的3.07GB(如果我没记错),然后尝试在初始调用者的上方4GB处分配本地内存块以创建新线程。

请问,只有在内存使用量接近或超过4GB时才会出现此问题吗?


还要检查您的永久代大小是否足够。 - ggb667
如果perm gen是一个问题,我会期望数百个工作站中的大部分都会遇到它,而不仅仅是偶尔出现的情况。 - Chaffers

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