如何避免巨大的查询结果导致 OutOfMemoryException
这是我的情况:
从我的 Grails 控制器中,我调用一个服务,该服务以只读方式查询数据库,将结果转换为 JSON,然后返回结果。
规格为:JDK 1.6、Tomcat 5.5、Grails 1.3.4、通过 JNDI
Tomcats 进行的 DB 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 和域验证中的一些泄漏,此处详细解释此处。我正要测试它,但我不知道这是否真的是我的问题的解决方案,以及(如果是的话)最好清理 GORM。
或者我的代码中是否存在其他内存泄漏? 有人有想法吗?
编辑:就我现在而言,异常发生在调用 finder 方法的地方。这意味着 GORM 无法处理数据库返回的数据量,对吧?很抱歉像新手一样提问,但我从未遇到过这样的问题,即使结果集非常大。
Here's my situation:
From my Grails controller, I call a service, which queries a database read-only, transforms the result into JSON, and returns the result.
Specs are: JDK 1.6, Tomcat 5.5, Grails 1.3.4, DB via JNDI
Tomcats MaxPermSize is set to 256m and Xmx to 128m.
EDIT: Increasing the memory should be the last resort
The service method:
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()
}
Now, when queryString == 'a' the result set is huge and I end up with this:
[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)
One approach I found on the web regards some leaks in Hibernate and domain validation, explained here and in detail here. I'm just about to test it, but I don't know if this is really the solution for my problem and (if it is) at which point it's best to clean up GORM.
Or is there another memory leak in my code?
Ideas anyone?
EDIT: As far as I am now, the exception occurs at the point where the finder method is called. That means that GORM isn't able to handle the amount of data returned by the database, right? Sorry for asking like a greenhorn, but I have never encountered such a problem, even with very large result sets.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
Sun(此链接不再有效) ) 已记录此
OutOfMemoryError
,如下所示:换句话说,该错误是一个功能,是增加可用内存的提示(正如您所提到的,这不是您的情况的首选选项)。一些开发人员考虑 此功能并非在所有用例中都有用,因此请检查将其关闭。
Sun (this link isn't valid anymore) had documented this
OutOfMemoryError
as follows:In other words, that error is a feature, a hint to increase available memory (which is not a preferred option in your case, as you've mentioned). Some developers consider this feature not to be useful in every use case, so check out turning it off.
已经建议的另一种选择是在结果页面中工作。不要使用动态查找器,而是使用条件并自行翻阅结果。这是一个简单的伪代码示例:
您可以根据内存要求调整批量大小。这将避免调整内存。
编辑
我刚刚重新阅读了弗莱奇的答案,注意到他提到这是一个解决方案,并且您对此发表了评论。我将把我的留在这里,因为它有一个示例,但是如果 Fletch 在他的示例中添加了一个分页示例,我将删除这个答案,因为他在我之前提到过它。
Another option to those already suggested would be to work in pages of results. Instead of using the dynamic finder, use Criteria and page through the results yourself. Here's a naive, pseudocode example:
You could tweak the batch size according to your memory requirements. This would avoid having to adjust the memory.
Edit
I just re-read Fletch's answer and noticed that he mentioned this as a solution and you commented on it. I'll leave mine here since it's got an example, but if Fletch adds a paging example to his, I'll delete this answer since he mentioned it before I did.
如果您不想增加内存,也许您应该只搜索大于一定数量的字符串。我想这是某种提前输入/建议功能;也许当有三个字符左右时您可以开始搜索。否则,也许分页结果是一种选择?
顺便说一句,从架构上来说,控制器旨在处理与外界及其格式的交互,即您可能希望您的服务仅返回对象,而控制器则进行 JSON 转换。但这并不能解决您当前的问题。
If you don't want to increase memory, maybe you should only search strings larger than a certain amount. I guess this is some kind of type-ahead/suggestion function; maybe you could start searching when there are three characters or so. Otherwise, maybe paged results is an option?
By the way architecturally the controller is intended to handle interaction with the outside world and its formats, i.e. you would probably want your service just to return the objects and your controller to do the JSON conversion. But this won't solve your current problem.
我还建议您仅通过此查询类型提前查询返回所需的属性,并获取包含用户所需的实际数据的完整域对象。
JSON 构建器将创建大量对象并消耗内存。例如,在用户的预先输入中,我只会返回基本名称信息和 id,而不是完整的对象
I would also suggest you only return the properties you need with this query type ahead query and get the full domain object with the actual data the user requires.
The JSON builder is going to create alot of objects and comsume memory. For example in a type-ahead for users, I would only return basic name information and an id instead of the complete object
在使用 MySQL 数据库的 Grails 应用程序中(MySQL 已通过 Homebrew 安装),我遇到了同样的问题,奇怪的是,只是在没有先启动 MySQL 服务器的情况下运行应用程序。因此,只需运行
mysql.server start
就解决了我的问题。
In a Grails app using a MySQL database (MySQL having been installed through Homebrew), I got this same problem, oddly enough, only by running the app without having started the MySQL server first. Thus simply running
mysql.server start
fixed the problem for me.