5

我们的记忆问题已经很久了。我终于找到了如何复制这个问题,但我不知道是什么导致了它或如何解决它。

我们在 Web 可访问的 /controller 目录中有许多 cfc,用于处理提交和处理。当不带方法参数直接调用 cfc 时,服务器开始占用内存。

例如,像http://www.domain.com/controller/LoginController.cfc这样的 URL将在浏览器中运行,直到超时。/CFIDE 已被锁定,无法公开访问

所以 cfexplorer 不(或不应该)可用。

我们使用 FusionReactor 来监控我们的实例。我们的服务器设置为 20GB 的堆空间。在加载应用程序后重新启动时,内存将在 800MB 左右徘徊。

在正常流量下,内存将在 5GB 和 10GB 之间波动,并定期进行垃圾收集。一段时间后,服务器最终达到 98% 的容量。它倾向于在那里运行

有时可以持续数小时甚至数天,直到某些流量高峰将其推倒并发生内存不足错误。垃圾回收没有恢复内存,也没有活动

FusionReactor 报告的长时间运行的线程。只有重新启动服务器才能恢复它。

使用 FusionReactor(我们刚刚安装了它,这就是我最终了解这个问题的方式)我正在检查 PermGen 内存空间,发现它占了

85% 的堆。这似乎根本不对。我执行了一次内存转储并通过 Eclipse 将其加载到 MAP 中进行分析。我发现内存中有10个对象

测量 1.7GB(1.7x10 约为总堆的 85%)。这些对象如下所示:

Class Name |  Shallow Heap | Retained Heap | Percentage
byte[1769628928] @ 0x4d963b198  ...128.................POST......../controller/LoginController.cfc......../controller/LoginController.cfc........173.14.93.66........173.14.93.66........www.domain.com........443........HTTP/1.1.......;D:\websites\domain\system\controller\Lo...| 1,769,628,944 | 1,769,628,944 |   8.60%

所以我在我们的一台服务器上重新启动了 CF。检查了 FusionReactor 并没有看到内存使用情况。然后转到浏览器并首先像这样调用cfc:

http://www.domain.com/controller/LoginController.cfc?method=foo

这导致 onMissingMethod 处理程序正确启动并重定向到适当的错误页面,而没有服务器影响。

但是然后调用这个:

http://www.domain.com/controller/LoginController.cfc

导致页面挂起。FusionReactor 报告没有活动请求,即使一个正在运行,这就是为什么我们无法在问题发生时识别问题。更糟糕的是,刷新内存会发现它缓慢增加了十分之一的百分比,而没有报告任何活动。服务器上的超时设置为 5 分钟。我假设它最终会被杀死,然后在 1.7GB 时成为孤儿。这并没有导致服务器宕机,只是增加了它现在运行的内存,其使用量仅为 3GB,垃圾收集无法恢复任何东西。这似乎可以解释为什么随着时间的推移,对这些 URL 的随机调用会慢慢地消化并保留内存。

接下来,我从多个浏览器选项卡中调用了 URL。这几乎瞬间将内存飙升至 98%。FusionReactor 现在显示两个长时间运行的请求 10 秒并且即使有超过 15 个浏览器选项卡在运行,它也在攀升。强制杀死线程似乎无济于事。只有重新启动服务器才能解决问题。

所以现在我已经明确地确定了这个问题(幻象线程在 PermGen 堆中创建了巨大的孤立对象)以及如何复制这个问题。

我不知道如何或为什么直接向 cfc 提出请求。可能是机器人或偶尔奇怪的浏览器行为。

所有巨大的对象都是 jrun.servlet.jrpp.ProxyEndpoint 的实例。

具体是什么导致了这个问题,我该如何解决。

这是运行 Java 1.7.0_25 的 Win2003 服务器上的 CF9.01 标准。

谢谢!

heap dump的MAP分析截图

4

3 回答 3

2

我相信这是 ColdFusion 中的一个合法错误,我已经通过他们的错误系统报告了它。该问题在其他系统上部分可重复。例如,在我在 Apache 上运行 CF 的 MBP 上,直接 CFC 调用不会导致内存问题,但会立即导致 JRun 'Internal Server Error' 页面。所以出现了问题,系统以不同的方式处理问题。安威...

感谢@iKnowKungFoo 和大量实验,我找到了一种解决方法。

将“方法”键/值插入 URL 范围似乎可以解决问题。需要注意的是,它必须在 onRequestStart 方法中完成,而不是在 onCFCRequest 方法中。从文档看来,对 CFC 的调用似乎会直接转到 onCFCRequest ,但情况似乎并非如此。所有请求都首先通过 onRequestStart 方法。只有当 onRequestStart 返回时才会调用 onCFCRequest 并且仅当存在所需的“方法”参数时。

因此,在这种情况下,无论如何都不会调用 onCFCRequest,因为“方法”参数从未存在过。所以这是立即在 onRequestStart 中运行的代码:

<cfif Right(arguments.targetPage,4) IS ".cfc"
      AND NOT StructKeyExists(URL,"WSDL")
      AND NOT StructKeyExists(URL,"method")
      AND NOT StructKeyExists(FORM,"method")>
    <cfset StructInsert(FORM,"method","")>
    <cfset StructInsert(URL,"method","")>
</cfif>

这段代码检查所请求页面上的扩展名,如果 URL 和 FORM 范围内都不存在方法参数,它会在两者中插入一个空白键/值对以进行良好测量。对“WSDL”参数的检查就在那里,因为我发现虽然这段代码运行良好,但突然间我们破坏了几个 web 服务 cfc 调用。如果对 cfc 的调用是 WebService.cfc?WSDL 则不需要方法参数,并且 CF 以不同的方式处理整个事情。

因此,插入空的“方法”值会导致在 onRequsetStart 完成时正确调用 onCFCRequest。当使用无效的空方法名称调用 cfc 时,onMissingMethod 现在已正确启动。该方法会迅速处理错误页面请求并重定向到自定义错误页面。

自从实施此修复程序以来,我们已经看到所有服务器上的内存使用率从一致的 98% 下降到 15%。内存图显示正在使用和收集的内存的预期锯齿。总体性能已经从 1200 毫秒的平均页面请求时间变为 54 毫秒,而所有这些请求都没有在后台猖獗地运行。

我仍然希望 Adob​​e 能够识别并解决问题。

于 2014-01-09T19:47:34.163 回答
1

也许您可以在 Application.cfc 中使用onCFCRequest来监控此问题。

它仍然会创建对象,但您可以记录请求,然后CFABORT应该停止请求死在其轨道上。

<cffunction name="oncfcRequest" returnType="void"> 
    <cfargument type="string" name="cfcname"> 
    <cfargument type="string" name="method"> 
    <cfargument type="struct" name="args"> 
    <cfif arguments.method IS "">
        <cflog .... />
        <cfabort />
    </cfif>
</cffunction>
于 2014-01-08T17:50:43.917 回答
0

我知道这将代表你做事方式的重大转变,但我一直避免让 CF 不必要地创建 CFC。除非他们改变了他们做事的方式(我上次玩过这个版本之前),否则直接点击 CFC 会导致创建一个新实例。

如果您准备进行小型测试,可以尝试设置一个简单的前端控制器/代表 .cfm 页面并将“控制器”内的 CFC 移动到应用程序范围。当然有更优雅的架构来处理它(没有迁移到一个完整的框架),但你可以:

使用 Application.cfc 将某些东西(如 LoginController)的实例设置到应用程序范围中,然后使用一个简单的“invoke.cfm”页面,该页面基本上期望这些应用程序范围的 CFC 之一的名称与参数一起调用。类似的东西(只是为了举例):

<cfsilent>
<cfset ctlName = url.controllerName />
<cfset methodName = url.methodName />
<cfset response = "" />

<!--- Look up the desired single-cfc controller --->
<cfif len(methodName) and structKeyExists( application.controllers, ctlName ) >
  <cfset ctl = application.controllers.ctlName />

  <!--- Now ask it do to something - note that i'm not validating the method... --->
  <cfinvoke component="#ctl#" method="#methodName#" argumentCollection="#form#" returnVariable="response" />
</cfif>
</cfsilent><cfoutput>#response#</cfoutput>  

请注意,这会导致您的“控制器”是有状态的,并且需要考虑线程安全(但无论如何应该已经是)。

于 2014-01-08T13:12:09.217 回答