使用 Quartz 调试 Groovy/Grails 应用程序中的堆空间问题
Posted
技术标签:
【中文标题】使用 Quartz 调试 Groovy/Grails 应用程序中的堆空间问题【英文标题】:Debugging Heap Space Problems in Groovy/Grails Application using Quartz 【发布时间】:2013-12-04 05:26:46 【问题描述】:我在 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 层应该不是问题。尽管如此,它仍然占据了堆的很大一部分。有什么想法吗?
有什么建议吗?
【问题讨论】:
【参考方案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 开发团队报告非常有用。
【讨论】:
实际上我什至自己在使用 Gmongo,而没有在两者之间使用 GORM(也将其添加到问题中)。感谢您的链接,我会对此进行调查,但是,这个问题似乎从未真正解决。我现在正在考虑直接使用官方 MongoDB 驱动程序替换我的 Gmongo 代码。 啊,堆空间设置为2GB。重新启动后,应用程序只使用了大约 400MB,所以这根本不成问题。 看来遇到同样问题的人也该这么做了。 我能够解决问题并将其放入答案中:)【参考方案2】:用户@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 中似乎存在内存泄漏,但可以通过重用连接来规避它。
希望这可以帮助某人节省一些时间。
【讨论】:
以上是关于使用 Quartz 调试 Groovy/Grails 应用程序中的堆空间问题的主要内容,如果未能解决你的问题,请参考以下文章