使用 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 应用程序中的堆空间问题的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Windows 服务中使用 Quartz.Net 安排任务?

quartz关闭DBUG日志

记一次Quartz重复调度(任务重复执行)的问题排查

记一次Quartz重复调度(任务重复执行)的问题排查

坑爹!Quartz 重复调度问题,你遇到过么?

Quartz使用 - 初识quartz