15

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

java.lang.OutOfMemoryError: 无法在 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,但它无法连接到进程(可能是线程耗尽的 b/c)。但是从我可以从 JVisualVM 获得的指标来看,Java 线程的数量大约是 22 个 live 和 11 个 daemon。

堆表现良好 - 堆为 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。一旦发生这种情况,一切都会崩溃。现在这是非常可重复的(将相同的流运行到 deflater 会导致内存激增)

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

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

4

2 回答 2

4

终于想通了。

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

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

这导致我们这样做:

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

在那之后的几个小时,系统终于陷入了无法摆脱的困境,无法在我们需要的时候创建本地线程。

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

所以绝对是原生资源泄漏。如果其他人曾经遇到过这样的问题,那么 VMMap 工具对于最终追踪导致问题的数据流是必不可少的。

于 2013-11-18T23:43:50.713 回答
0

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

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

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

您能否确认仅当内存使用量在 4gb 左右或以上时才会出现此问题?

于 2013-11-15T17:40:37.160 回答