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

2022-09-03 16:02:53

我们有一个广泛部署的应用程序(运行它的数百个工作站)。在一个站点(并且只有一个站点 - 我们的产品被广泛部署到许多环境中),我们随机收到以下错误:

java.lang.OutOfMemoryError: 无法在 java.lang.Thread.start0(Native Method) at java.lang.Thread.start(未知来源)创建新的原生线程

操作系统是Windows 7 64位我们在32位JVM中运行( 1.7.0_45)

使用Windows任务管理器,我可以看到该进程有39个本机线程(不是很多),因此我们的应用程序中没有线程泄漏...没有其他进程消耗大量线程(资源管理器有35个,jvisualvm有24个,iexplore有20个,...我没有确切的计数,但我们可能会查看用户总数的300个线程)。

我试图附加JVisualVM,但它无法连接到进程(可能是线程耗尽的b / c)。但是从我从JVisualVM获得的指标来看,Java线程的数量大约是22个实时和11个守护进程。

堆表现良好 - 堆为 500MB,实际使用了 250MB。

该过程使用-Xmx512m启动

我们的进程显示内存使用量(在任务管理器中)为 597,744K。

工作站有8GB RAM,其中只使用3.8-4.0GB(我知道,32位进程无法访问所有这些,但仍然有很多)

使用 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崩溃了“无法创建新的原生线程” - 有什么想法吗?(我的设置和信息里面)

他们认为堆栈大小可能是问题所在。

本文:在哪里可以找到 Sun/Oracle JVM 的默认 XSS 值? - 提供了一个指向 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 上允许的线程数。

我将假设:

操作系统进程空间限制: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。一旦发生这种情况,一切都会分崩离析。这现在是非常可重复的(将相同的流运行到放气器中会导致内存峰值)

那么,我们是否可能正在研究JRE的zip库的问题?认为这样似乎很疯狂,但我真的很茫然。

如果我采用完全相同的流并在不同的系统上运行它(即使运行相同的JRE - 32位,Java 7u45),我们也不会遇到问题。我已经完全卸载了JRE并重新安装了它,没有任何行为变化。


答案 1

终于弄清楚了。

我们处理了几个数据流(在这个站点上,1000万个数据流中有4个),最终创建了大量的DefllaterOutputStream对象。我们使用的第三方库在流上调用 finish() 而不是 close()。底层的Defllater终结器正在清理东西,所以只要负载不是太高,就不会有问题。但过了一个临界点,我们开始遇到这种情况:

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

这导致我们这样做:

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

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

修复方法是让第三方库关闭DefllaterOutputStream。

所以绝对是原生资源泄漏。如果还有其他人遇到类似情况,VMMap工具对于最终跟踪导致问题的数据流是必不可少的。


答案 2

我怀疑,虽然显然很难证明,你遇到了一个32位内存分配问题。

线程分配了本机内存,而不是堆内存,堆内存必须是连续的,才能运行。虽然我确信WOW64允许32位进程在4gb以上的区域中运行,但我不太确定如果使用干预空间,是否为超过4gb限制的新线程分配本机内存。

因此,您的应用程序和堆处于低mem中,其他进程正在采用中间的3.07gigs(如果内存服务),然后尝试在初始调用方上方分配一个4gb的本机内存块以创建新线程。

您能否确认仅当内存使用量接近或高于4gb标记时才发生此问题?