在没有方法参数的情况下直接调用cfc时,ColdFusion内存泄漏

时间:2014-01-08 09:45:08

标签: java memory memory-leaks coldfusion fusionreactor

我们长期以来一直有记忆问题。我终于找到了如何复制问题的方法,但我不知道是什么导致它或如何修复它。

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

例如,http://www.domain.com/controller/LoginController.cfc之类的网址将在浏览器中运行,直到超时为止。 / 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%。尽管有超过15个浏览器标签在运行,但FusionReactor现在显示两个长时间运行的请求10秒并攀升。强行杀死线程似乎无能为力。只有服务器重启才能解决问题。

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

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

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

具体导致此问题的原因以及如何解决此问题。

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

谢谢!

Screenshot of MAP analysis of heap dump

3 个答案:

答案 0 :(得分:2)

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

由于@iKnowKungFoo和大量实验,我找到了解决方法。

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

所以在这种情况下,onCFCRequest从未被调用过,因为'method'参数从未存在过。所以这里是立即在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,则不需要method参数,CF会以不同的方式处理整个事件。

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

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

我仍然希望Adobe能够识别并解决问题。

答案 1 :(得分: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>

答案 2 :(得分: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>  

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