ColdFusion JVM垃圾回收问题

4

我有一个cfc方法,它正在循环遍历列表并通过cfhttp进行一系列SOAP调用。然后将结果插入到数据库中。

这个过程本身运作良好,问题在于Java内存会缓慢填满,并最终停止工作(具体取决于返回记录中的元素数量)。没有错误或任何可见的东西,它就会停止。如果我通过Coldfusion管理界面查看应用程序日志文件,我会看到以下一种或两种错误:

GC overhead limit exceeded The specific sequence of files included or processed is:

或者

Java heap space The specific sequence of files included or processed is:

以下是我正在运行的代码的简化版本:
<cfsetting requesttimeout="3600">
<cfloop condition="thisPass lt 10">
    <cfscript>
        runtime = CreateObject("java","java.lang.Runtime").getRuntime();
        objSystem = CreateObject( "java", "java.lang.System" );
        soapBody = '';
        soapResponse = '';
        thisStruct = '';
        lock scope='application' type='exclusive' timeout='60' {


//This is where I am trying to manage the memory and call garbage collection


        try {
            freeMemory = runtime.freeMemory()/1024/1024;
            writeOutput("- fm = "&freeMemory);
            if (freeMemory < 200){
                objSystem.gc();
                sleep(1000);
                writeDump(' - dumping freeMemory');
             }
         }
         catch(any error) {
            writeDump(' - trying to dump GC as '&now()& ' freeMemory = '&freeMemory);
         }
         }
        </cfscript>
        <cfsavecontent variable="soapBody">
            <?xml version="1.0" encoding="utf-8"?>
            [ BUILD SOAP ENVELOP ]
        </cfsavecontent>

        <cfhttp url="[URL]" method="post" result="httpResponse" 
                        timeout="600" resolveurl="false">
                <cfhttpparam type="header" name="SOAPAction" value="[URL2]" />
                <cfhttpparam type="xml" value="#trim( soapBody )#"/>
            </cfhttp>


            <cfscript>
                soapBody = "";
                soapResponse = httpResponse.fileContent;
                soapResponse = xmlParse( soapResponse );
                thisStruct = xmlSearch(soapResponse,'/soap:Envelope/soap:Body/')[1].xmlChildren[1].xmlChildren[1].xmlChildren;
                writeOutput("-"&arrayLen(thisStruct)&' records');
                getPageContext().getOut().flush();
                if(arrayLen(thisStruct) == 2500){
                    thisPass = thisPass+1;
                } else {
                    writeOutput("- total records = "&(2500*(thisPass-1))+arrayLen(thisStruct));
                    thisPass = 100; // since looping while thisPass lt 10 this should prevent the next iteration
                }
            </cfscript>

            <cfloop from="1" to="#arrayLen(thisStruct)#" index="i">
                [RUN PROC TO INSERT RECORDS]
            </cfloop>
        </cfloop>

GC似乎有时会释放一些内存,但不可靠。我知道GC()只是建议Java释放一些未使用的内存,但我不确定如何强制其释放内存。可能存在某个泄漏,但我没有看到它。我希望这是一些明显的问题,我承认我的Java知识极其有限。
有没有什么Java大师能看到我的错误?
更新:以下是输出示例,以便查看内存下降是否有帮助。
有236个列表需要循环遍历。
  1. 88185 - fm = 293.564407349 -6条记录- 总记录数=6
  2. 88389 - fm = 290.86995697 -116条记录- 总记录数=116
  3. 88390 - fm = 308.382568359 -262条记录- 总记录数=262
  4. 88839 - fm = 292.707099915 -2032条记录- 总记录数=2032
  5. 91088 - fm = 290.711753845 -6条记录- 总记录数=6
  6. 92998 - fm = 287.754066467 -5条记录- 总记录数=5
  7. 95510 - fm = 309.919425964 -91条记录- 总记录数=91
  8. 96478 - fm = 292.035064697 -1180条记录- 总记录数=1180
  9. 96479 - fm = 259.001213074 -1113条记录- 总记录数=1113
  10. 96480 - fm = 261.121406555 -110条记录- 总记录数=110
  11. 96796 - fm = 267.235244751 -2条记录- 总记录数=2
  12. 96799 - fm = 265.037582397 -0条记录- 总记录数=0
  13. 97435 - fm = 263.589103699 -2500条记录 - fm = 227.629760742 -2500条记录 - fm = 200.85987854 -2500条记录 - fm = 202.156776428 -2500条记录 - fm = 166.366210938 - dumping freeMemory -656条记录- 总记录数=10656
  14. 98173 - fm = 160.579734802 - dumping freeMemory -35条记录- 总记录数=35
  15. 99111 - fm = 176.218482971 - dumping freeMemory -0条记录- 总记录数=0
  16. 100998 - fm = 194.708694458 - dumping freeMemory -185条记录- 总记录数=185
  17. 101811 - fm = 160.61415863 - dumping freeMemory -2500条记录 - fm = 112.862670898 - dumping freeMemory -2500条记录 - fm = 86.2071380615 - dumping freeMemory -2500条记录 - fm = 52.9639358521 - dumping freeMemory -1064条记录- 总记录数=8564
  18. 105014 - fm = 56.1721343994 - dumping freeMemory -14条记录- 总记录数=14
  19. 105992 - fm = 73.0022964478 - dumping freeMemory -14条记录- 总记录数=14
  20. 107539 - fm = 75.9522399902 - dumping freeMemory -93条记录- 总记录数=93
  21. 107580 - fm = 58.345199585 - dumping freeMemory -2500条记录

你为什么要试图以编程方式管理垃圾回收?我也不是Java大师,但这似乎是最好留给操作系统处理的事情。调整JVM设置是适当的。 - Miguel-F
我正在尝试管理它,因为它本身并没有很好地进行管理。这个过程需要10-15分钟的时间,虽然有一些管理工作正在进行,但还不足以防止最终的jrun崩溃。同时也想问一下,是否有可能存在内存泄漏问题。 - Lance
每次循环迭代都需要设置runtime和objSystem吗? - antony.trupe
不,我这样做是为了尝试清除那个内存。没有帮助。 - Lance
4个回答

1

经过大量搜索,我发现解决这个特定问题的最佳方法是删除尝试进行垃圾回收并增加Java堆大小的代码。在/jrun/bin/jvm.config文件中。

通过更改VM参数为:

java.args=-server -Xms2048m -Xmx2048m -Xmn1024m -Dsun.io.useCanonCaches=false -XX:MaxPermSize=192m -XX:+UseParallelGC -Xbatch -Dcoldfusion.rootDir={application.home}/ -Djava.security.policy={application.home}/servers/cfusion/cfusion-ear/cfusion-war/WEB-INF/cfusion/lib/coldfusion.policy -Djava.security.auth.policy={application.home}/servers/cfusion/cfusion-ear/cfusion-war/WEB-INF/cfusion/lib/neo_jaas.policy

我能够增加初始堆大小(Xms)和最大堆大小(Xmx)到2048m,以及“年轻代”堆大小(Xmn)到1024m。因为有人建议年轻代应该比初始堆和最大堆小,以便更好地进行垃圾回收。
正如James所建议的那样,我注释掉了实际的进程(这些进程在函数和变量中),然后逐个取消注释并每次运行所有内容。通过这个过程我学到的是大型SOAP响应填满了内存,而不是像我担心的那样发生了泄漏。
正如Adam所提到的,问题不在于Java管理GC,而是没有足够的空间来处理我所面临的问题(由于某种原因CF无法很好地处理2500条记录的SOAP响应)。
亚当说CF中的Java内存故障排除是一种“黑暗的艺术”,这也是正确的。通过使用服务器监视器http://localhost/CFIDE/administrator/monitor/launch-monitor.cfm并转到内存使用情况下的统计选项卡 - >内存使用情况摘要,我可以观察到即使在重新启动后没有运行任何进程时,内存也会缓慢填充并且自行转储。我从未能够弄清楚为什么,但我可以看到正在运行的级别以及我达到了顶部。
jvm.config文件中分配的默认内存为512m,简单地不足以处理正在发生的事情。可能有更好的方法来处理这个整体过程。可能必须实现亨利的建议并将其放入数据库中并进行块处理,尽管这对我来说听起来非常笨拙。
我确信除了CF现在从一开始就占用大量资源之外,还可能存在其他问题,但(目前)它似乎按需要工作。
更新: 我更改了cfc函数,以便不是将所有内容插入数据库,而是将所有XML写入文件,然后读取文件并将其插入数据库。不知何故,写入文件允许Java足够长时间“呼吸”以执行GC()。

把它放进数据库并分块处理,虽然这听起来很笨拙。批处理比把所有东西放在一个循环中提供了更多的控制。通过表驱动过程,您可以精确地控制处理多少数据 - 以及何时处理,并轻松暂停或停止处理。当然,相对于只是把所有东西扔进一个循环中,编码时需要多些思考。但是,总体而言,它提供了更大的控制能力。 - Leigh

1
这不是Java管理GC能力不好的问题,而是你没有以让垃圾回收器清理它们的方式来管理内存中的内容(或者从内存中取出)。你在尝试治疗症状而非问题本身。
查看一下放入内存的内容以及为什么你期望被GC的东西却没有被GC。可能是你意外地引用了共享作用域中的内容,或类似的原因。在Java上使用ColdFusion进行此类故障排除有点玄学。
但不要试图“修复”强制GC时未被GC的“问题”,而是解决导致内存a)填满;b)在你认为应该被GC时无法进行GC的问题。

这就是为什么我发布了所有的代码,而不仅仅是我(试图)管理内存的部分。我希望有人能够看到我在迭代之间没有清除内存的问题。 - Lance

1
我可以说,当我尝试过强制GC方法时,并没有对我产生太大的帮助。一般来说,以下是一些首要尝试的事项:
  • 确保在调用变量的对象中使用var或local
  • 如果可以将处理移动到数据库,请这样做
  • 删除,非常重。如果您真的需要使用它,请仅关注所需的变量部分。还要使用文本格式
  • 更改JVM以使用更多内存
  • 使用Java 1.7和G1GC。(请谨慎执行此操作,因为它可能尚未得到支持)
  • 使用较少的查询

以下是我考虑您上面代码的清单

  • 我会将循环内的所有内容移动到函数和var范围soapbody soapResponse, thisStruct中
  • 完成thisStuct后,我会StructClear(thisStruct);
  • 去掉writedump();,它们像一样沉重

我将writeDump更改为writeOutput,并在循环末尾添加了ArrayClear(thisStruct),我知道名称很奇怪,但thisStruct是一个数组。不幸的是,没有任何改进。 - Lance
在这个阶段,您需要确定哪些操作导致了最多的内存使用。a)构建SOAP信封,b)CFHTTP调用,c)XML解析,d)XML搜索,或e)[运行过程以插入记录]。删除除a之外的所有内容,并检查内存使用情况,然后是a和b等等。找出哪些正在消耗未被释放的内存。将该过程更改为函数并使用var仍然是一个好主意。 - James A Mohler

0
据我所知,CF不擅长处理长时间的请求。据我所听,内存在请求完成之前不会释放。
我们尝试将长时间的请求拆分成CF可以管理的较小请求,一般情况下内存会在请求完成后被释放。
我们使用的一个传统系统会将任务插入DB表中,然后CF Scheduler将按批处理工作。我讨厌这个系统因为它有延迟,但这是在CF7时代需要做的,而且似乎自那时以来也没有改善。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接