我在Groovy / Grails中创建了一个小应用程序,它使用Quartz每10秒执行一次小作业。现在我遇到的问题是,运行几个小时后,应用程序崩溃了org.quartz.JobExecutionException: java.lang.OutOfMemoryError: Java heap space [See nested exception: java.lang.OutOfMemoryError: Java heap space]
。
现在我正试图使用Eclipse Memory Analyzer找到问题的原因。通过找到“问题嫌疑人”,分析仪显示了这一结果:
Problem Suspect 1
3,926 instances of "groovy.lang.ExpandoMetaClass",
loaded by "org.codehaus.groovy.grails.cli.support.GrailsRootLoader @ 0x122e88b98"
occupy 95,746,168 (33.69%) bytes.
Keywords
org.codehaus.groovy.grails.cli.support.GrailsRootLoader @ 0x122e88b98
groovy.lang.ExpandoMetaClass
--
Problem Suspect 2
1,010 instances of "com.mongodb.DBApiLayer",
loaded by "org.codehaus.groovy.grails.cli.support.GrailsRootLoader @ 0x122e88b98"
occupy 56,522,416 (19.89%) bytes.
These instances are referenced from one instance of
"org.codehaus.groovy.util.AbstractConcurrentMapBase$Segment[]", loaded by
"org.codehaus.groovy.grails.cli.support.GrailsRootLoader @ 0x122e88b98"
Keywords
org.codehaus.groovy.grails.cli.support.GrailsRootLoader @ 0x122e88b98
org.codehaus.groovy.util.AbstractConcurrentMapBase$Segment[]
com.mongodb.DBApiLayer
在Groovy(和Grails)应用程序中拥有ExpandoMetaClass
的那么多实例是否正常,或者这可能是我介绍的问题?
关于MongoDB:应用程序使用GORM 和直接使用Gmongo从数据库中读取和写入许多小项。但是,我已经检查了所有连接,并且在一段时间后它们正确关闭。活动线程的大概数量约为40.所以我认为DB层不应该是问题。但是,它需要很大一部分堆。有什么想法?
有什么建议吗?
答案 0 :(得分:1)
这实际上可能是GMongo驱动程序的问题。 a thread in the gmongo Github具有类似环境的用户具有非常相似的问题。
Gmongo是grails mongo插件(see this source)使用的驱动程序。
如果可以,请尝试使用MongoDB驱动程序本身,而不是Gmongo或它依赖的Grails插件。
作为一种解决方法,并且作为隔离问题的一种方法,您可以尝试增加堆大小;如果您目前正在人为地缩小堆大小,这尤其是一个好主意。
export GRAILS_OPTS="-Xmx1G -Xms256m -XX:MaxPermSize=256m"
grails run-app
如果内存消耗在某一点处平稳,则可能会停止出现内存不足错误。如果没有,那么增加堆大小应该比延迟不可避免的更多。跟踪相对于堆大小的崩溃时间对于向gmongo开发团队报告是非常有用的。
答案 1 :(得分:1)
用户@jonnybot表示问题可能是由GMongo造成的,事实上他是对的。我开始与GMongo的创建者进行讨论,在其中我创建了一个小应用程序,每秒都会在MongoDB集合中插入一些东西,以便能够复制问题。该应用程序显示大量内存泄漏:
class MemoryJob {
def concurrent = false
static triggers = {
simple startDelay: 5000, repeatInterval: 1000
}
def execute() {
def mongoUrl = "mongodb://localhost:27017"
def mongo = new GMongo(new MongoURI(mongoUrl))
def db = mongo.getDB("memory")
println new Date()
db.getCollection("test").insert(['date':new Date()])
mongo.close()
}
}
GMongo的作者随后建议重用数据库连接,而不是在每个请求上创建新连接。例如:
class MemoryJob {
def concurrent = false
static triggers = {
simple startDelay: 5000, repeatInterval: 200
}
static mongoUrl = "mongodb://localhost:27017"
static mongo = new GMongo(new MongoURI(mongoUrl))
static db = mongo.getDB("memory");
def execute() {
println new Date()
db.getCollection("test").insert(['date':new Date()])
}
}
事实上,这解决了我遭受的内存泄漏。
作为结论:不要在每个请求上创建MongoDB连接,而是重用单个连接。虽然GMongo中似乎存在内存泄漏,但可以通过重用连接来规避它。
希望这有助于节省一些时间。