【问题标题】:How to avoid a huge query result causing an OutOfMemoryException如何避免导致 OutOfMemoryException 的巨大查询结果
【发布时间】:2010-11-03 10:15:33
【问题描述】:

这是我的情况:

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

规格是:JDK 1.6、Tomcat 5.5、Grails 1.3.4、DB via 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 无法处理数据库返回的数据量,对吧?很抱歉像新手一样问,但我从未遇到过这样的问题,即使结果集非常大。

【问题讨论】:

  • 您希望此查询返回多少结果?
  • 一个“a”会返回数千个结果。
  • 128m 的 Xmx 对于 grails 应用程序来说听起来相当小,如果可能的话,我肯定想提高它。此外,MaxPermSize 相对于 Xmx 大小相当大;可能值得尝试使用较大 Xmx 的较低 permgen。

标签: grails groovy service grails-orm


【解决方案1】:

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

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

换句话说,该错误是一项功能,是增加可用内存的提示(正如您所提到的,这不是您的首选选项)。一些开发人员consider此功能并非在所有用例中都有用,因此请检查是否将其关闭。

【讨论】:

  • 你是对的,这个选项不再有 OutOfMemoryException ......但是查询非常慢:) 现在我正在分块检索结果(如 Fletch 建议的)以避免异常并保持性能达到可接受的水平;
【解决方案2】:

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

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

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

编辑

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

【讨论】:

  • 并非绝对需要使用 Criteria 来获取分页:您可以将 max 和 offset 传递到动态查找器中,例如 SomeDomain.findAllBySomePropIlike("%${queryString}%", [max: 50, offset: offset])
  • @ataylor - 很好,感谢您的提醒。我会更新我的答案。
【解决方案3】:

如果你不想增加内存,也许你应该只搜索大于一定数量的字符串。我想这是某种预先输入/建议功能;也许您可以在大约三个字符时开始搜索。否则,也许分页结果是一种选择?

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

【讨论】:

  • 关于架构的好主意和要点;我现在正在做一些寻呼。提前输入本来是完美的,但查询字符串并非直接来自用户交互。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

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

    mysql.server start

    帮我解决了这个问题。

    【讨论】:

      猜你喜欢
      • 2017-08-17
      • 2022-01-19
      • 1970-01-01
      • 1970-01-01
      • 2015-06-29
      • 2018-04-06
      • 1970-01-01
      • 2018-05-03
      • 2022-07-22
      相关资源
      最近更新 更多