如何避免导致OutOfMemoryException的巨大查询结果

时间:2010-11-03 10:15:33

标签: grails groovy service gorm

这是我的情况:

从我的Grails控制器中,我调用一个服务,它以只读方式查询数据库,将结果转换为JSON,然后返回结果。

规格是:JDK 1.6,Tomcat 5.5,Grails 1.3.4,DB通过JNDI

Tomcats MaxPermSize设置为256m,Xmx设置为128m 编辑:增加内存应该是最后的手段

服务方法:

  String queryDB(String queryString) {
    StringWriter writer = new StringWriter()
    JSonBuilder json = new JSonBuilder(writer)
    def queryResult = SomeDomain.findAllBySomePropIlike("%${queryString}%")

    json.whatever {
      results {
        queryResult.eachWithIndex { qr, i ->
          // insert domain w/ properties
        }
      }
    }
    queryResult = null
    return writer.toString()
  }

现在,当queryString =='a'时,结果集很大,我最终得到了这个:

[ERROR] 03/Nov/2010@09:46:39,604 [localhost].[/grails-app-0.1].[grails] - Servlet.service() for servlet grails threw exception
java.lang.OutOfMemoryError: GC overhead limit exceeded
    at org.codehaus.groovy.util.ComplexKeyHashMap.init(ComplexKeyHashMap.java:81)
    at org.codehaus.groovy.util.ComplexKeyHashMap.<init>(ComplexKeyHashMap.java:46)
    at org.codehaus.groovy.util.SingleKeyHashMap.<init>(SingleKeyHashMap.java:29)
    at groovy.lang.MetaClassImpl$Index.<init>(MetaClassImpl.java:3381)
    at groovy.lang.MetaClassImpl$MethodIndex.<init>(MetaClassImpl.java:3364)
    at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:140)
    at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:190)
    at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:196)
    at groovy.lang.ExpandoMetaClass.<init>(ExpandoMetaClass.java:298)
    at groovy.lang.ExpandoMetaClass.<init>(ExpandoMetaClass.java:333)
    at groovy.lang.ExpandoMetaClassCreationHandle.createNormalMetaClass(ExpandoMetaClassCreationHandle.java:46)
    at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:139)
    at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
    at org.codehaus.groovy.runtime.callsite.ClassMetaClassGetPropertySite.<init>(ClassMetaClassGetPropertySite.java:35)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.createClassMetaClassGetPropertySite(AbstractCallSite.java:308)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.createGetPropertySite(AbstractCallSite.java:258)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.acceptGetProperty(AbstractCallSite.java:245)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGetProperty(AbstractCallSite.java:237)
    at org.codehaus.groovy.grails.plugins.web.filters.FilterToHandlerAdapter.accept(FilterToHandlerAdapter.groovy:196)
    at org.codehaus.groovy.grails.plugins.web.filters.FilterToHandlerAdapter$accept.callCurrent(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:44)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:143)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:159)
    at org.codehaus.groovy.grails.plugins.web.filters.FilterToHandlerAdapter.preHandle(FilterToHandlerAdapter.groovy:107)
    at org.springframework.web.servlet.HandlerInterceptor$preHandle.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:40)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
    at org.codehaus.groovy.grails.plugins.web.filters.CompositeInterceptor.preHandle(CompositeInterceptor.groovy:42)
    at org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet.doDispatch(GrailsDispatcherServlet.java:282)

我在网上发现的一种方法是关于Hibernate和域验证中的一些漏洞,解释here和详细here。我正准备测试它,但我不知道这是否真的是我的问题的解决方案(如果是),最好是清理GORM。

或者我的代码中是否还有其他内存泄漏? 任何想法?

编辑:就我而言,异常发生在调用finder方法的位置。这意味着GORM无法处理数据库返回的数据量,对吧?很抱歉像新手一样问,但我从来没有遇到过这样的问题,即使结果非常大。

5 个答案:

答案 0 :(得分:4)

Sun(此链接无效)had documentedOutOfMemoryError如下:

  

并行/并发收集器   如果是的话也会抛出OutOfMemoryError   垃圾花了很多时间   收集:如果超过98%的   总时间花在垃圾上   收集和不到2%的   堆被恢复,OutOfMemoryError   将被抛出。这个功能是   旨在防止应用程序   跑了很长一段时间   虽然很少或没有进展   因为堆太小了。如果   必要的,这个功能可以   通过添加选项禁用    -XX:-UseGCOverheadLimit 到命令行。

换句话说,该错误是一个功能,一个增加可用内存的提示(如您所述,这不是您的情况下的首选选项)。一些开发人员consider此功能在每个用例中都没有用,所以请查看关闭它。

答案 1 :(得分:3)

已经提出的建议的另一个选择是在结果页面中工作。不使用动态查找器,而是使用Criteria并自行翻译结果。这是一个天真的伪代码示例:

def offset = 0
def max = 50
while(stillMoreResults) {
    def batch = SomeDomain.findAllBySomePropIlike("%${queryString}%", [max: max, offset: offset])
    appendBatchToJsonResult(batch)
    offset += max
}

您可以根据内存要求调整批量大小。这样可以避免调整内存。

修改

我刚刚重新阅读了Fletch的回答,并注意到他提到这是一个解决方案,你对此发表了评论。我会离开我的,因为它有一个例子,但如果Fletch给他添加了一个分页示例,我会删除这个答案,因为他在我之前提到过。

答案 2 :(得分:2)

如果您不想增加内存,可能只应搜索大于一定数量的字符串。我想这是某种提前/建议功能;也许你可以在有三个字符左右时开始搜索。否则,分页结果可能是一个选项吗?

顺便说一下,在架构上,控制器旨在处理与外部世界及其格式的交互,即您可能希望您的服务只是返回对象和控制器以进行JSON转换。但这不能解决你目前的问题。

答案 3 :(得分:1)

我还建议您只使用此查询类型提前查询返回所需的属性,并使用用户需要的实际数据获取完整的域对象。

JSON构建器将创建大量对象和comsume内存。例如,在用户的预先输入中,我只返回基本名称信息和id而不是完整对象

答案 4 :(得分:0)

在使用MySQL数据库的Grails应用程序(MySQL已通过Homebrew安装)中,奇怪的是,我只是通过运行应用程序而没有首先启动MySQL服务器而遇到了同样的问题。因此只需运行

mysql.server start

为我解决了这个问题。