我和我的团队在过去 6 个月的大部分时间里一直在努力保持集群 ColdFusion 应用程序的稳定性,但收效甚微。我们求助于 SF,希望找到一些 JRun 专家或新想法,因为我们似乎无法找到解决办法。
设置:
两个 ColdFusion 7.0.2 实例在 Windows Server 2003 下的 IIS 6 上与 JRun 4(带最新更新)集群。两个四核 CPU,8GB RAM。
问题:
时不时地,通常每周一次,其中一个实例会完全停止处理请求。它上面没有任何活动,我们必须重新启动它。
我们知道的是:
每次发生这种情况时,JRun 的错误日志总是充满 java.lang.OutOfMemoryError:无法创建新的本机线程。
在阅读了 Macromedia/Adobe 的 JRun 文档和许多令人困惑的博客文章后,我们或多或少将其缩小到实例的 jrun.xml 中不正确/未优化的 JRun 线程池设置。
我们的jrun.xml的相关部分:
<service class="jrun.servlet.jrpp.JRunProxyService" name="ProxyService">
<attribute name="activeHandlerThreads">500</attribute>
<attribute name="backlog">500</attribute>
<attribute name="deactivated">false</attribute>
<attribute name="interface">*</attribute>
<attribute name="maxHandlerThreads">1000</attribute>
<attribute name="minHandlerThreads">1</attribute>
<attribute name="port">51003</attribute>
<attribute name="threadWaitTimeout">300</attribute>
<attribute name="timeout">300</attribute>
{snip}
</service>
我上周启用了 JRun 的指标日志记录来收集与线程相关的数据。这是记录一周后的数据摘要。
平均值:
{jrpp.listenTh} 1
{jrpp.idleTh} 9
{jrpp.delayTh} 0
{jrpp.busyTh} 0
{jrpp.totalTh} 10
{jrpp.delayRq} 0
{jrpp.droppedRq} 0
{jrpp.handledRq} 4
{jrpp.handledMs} 6036
{jrpp.delayMs} 0
{freeMemory} 48667
{totalMemory} 403598
{sessions} 737
{sessionsInMem} 737
最大值:
{jrpp.listenTh} 10
{jrpp.idleTh} 94
{jrpp.delayTh} 1
{jrpp.busyTh} 39
{jrpp.totalTh} 100
{jrpp.delayRq} 0
{jrpp.droppedRq} 0
{jrpp.handledRq} 87
{jrpp.handledMs} 508845
{jrpp.delayMs} 0
{freeMemory} 169313
{totalMemory} 578432
{sessions} 2297
{sessionsInMem} 2297
我们现在可以尝试什么吗?
干杯!
编辑#1-> 我忘记提及的事情:Windows Server 2003 Enterprise w/ JVM 1.4.2(用于 JRun)
是的,最大堆大小约为 1.4GB。我们以前有过泄漏,但我们修复了它们,现在应用程序使用量约为 400MB,很少超过。最大堆大小设置为 1200MB,所以我们没有达到这个值。当我们确实有泄漏时,JVM 会崩溃,实例会自行重启。现在不会发生这种情况,它只是停止处理传入请求。
我们认为它与此博客文章后面的线索有关: http://www.talkingtree.com/blog/index.cfm/2005/3/11/NewNativeThread
抛出的 Java 异常类型为 OutOfMemory,但实际上并不是说我们用完了堆空间,只是说无法创建新线程。异常类型有点误导。
基本上,博客说 500 作为 activeHandlerThreads 可能太高了,但我的指标似乎表明我们远远没有达到这个数字,这让我们感到困惑。
答案1
好吧,在了解 JRun 配置细节之前,让我们先看一些更大的问题。
如果您在 JRun 错误日志中收到 java.lang.OutOfMemoryError 异常,那么,您的内存不足。请不要为此点赞 ;-)。您没有说您运行的是 32 位还是 64 位 Windows,但您确实说您有 8 GB 的 RAM,因此这会对答案产生一些影响。您运行的是 32 位还是 64 位 JVM(以及哪个版本)也会产生影响。因此,这些答案将帮助我们彻底解决这个问题。
无论如何,您的应用程序确实会耗尽内存。它耗尽内存的原因有以下一个或多个:
- 您的应用程序正在泄漏内存。您的应用程序使用的某些对象不断被引用,因此永远无法进行垃圾回收;或者更糟的是,每次请求时新建的某些对象永远被另一个对象引用,因此永远无法进行垃圾回收。在这方面,正确的 J2EE 会话处理可能特别棘手。
- 处理每个并发请求所需的内存量(在配置的并发请求级别)超出了 JVM 堆中可用的内存量。例如,您的堆大小为 1 GB,每个请求最多可使用 10 MB。您的应用服务器已调整为允许 150 个并发请求。(我知道这些数字太简单了)。在这种情况下,如果您在负载下遇到 100 个或更多并发请求(如果每个请求都使用了满足请求所需的最大内存量),那么您肯定会耗尽内存。
需要注意的其他事项:在 32 位 Windows 上,32 位 JVM 只能分配大约 1.4 GB 的内存。我不记得 64 位 Windows 上的 32 位 JVM 是否有小于任何 32 位进程的理论最大值 4 GB 的限制。
更新
我阅读了 TalkingTree 链接的博客文章以及该文章中链接的其他文章。我没有遇到过这种情况,但我确实有以下观察:JRUN 指标日志记录可能不会记录您在线程使用高峰期引用的“最大值”。我认为它以固定的、重复的间隔记录指标。这有利于向您展示应用程序平稳、平均的性能特征,但它可能无法在错误情况开始发生之前捕获 JRUN 的状态。
尽管不知道 JRUN 线程管理的内部工作原理,我仍然认为它确实内存不足。也许它不是因为您的应用需要在 JVM 堆上分配内存而没有可用内存,而是因为 JRUN 尝试创建另一个线程来处理传入请求,而支持另一个线程所需的堆内存不可用,所以它内存不足 - 换句话说,线程不是空闲的 - 它们也需要堆内存。
您的选择似乎如下:
- 减少应用程序在每个请求中使用的内存量,或者-
- 尝试降低 JRUN 配置中的线程调整参数的值,以使更多线程排队进行处理,而不是同时进入运行状态,或者-
- 减少 ColdFusion 管理器中的同时请求数(请求调整页面,字段“同时请求的最大模板数”)
无论您选择哪种方案,我认为有效的解决方案都是实验性的。您必须进行更改,然后查看它对应用程序有何影响。您有一个负载测试环境,对吗?
答案2
尝试减少最大堆大小。每个线程都需要本机资源(以及 Java 自己的资源)。可用的虚拟 AS 为 2GB;1.2GB 为堆保留。剩余 800MB 的一部分用于代码(Java 的文本段和所有必需的 DLL),然后是 JRE 及其依赖项所需的本机分配……以及线程:默认情况下,每个线程保留 1MB 的 AS(尽管实际上只提交了一个页面),100 个线程 = 100MB(仅用于堆栈)。现在在各个部分之间添加一些额外的空间,一些碎片……OOM ;-)