【问题标题】:Debugging Heap Space Problems in Groovy/Grails Application using Quartz使用 Quartz 调试 Groovy/Grails 应用程序中的堆空间问题
【发布时间】: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 层应该不是问题。尽管如此,它仍然占据了堆的很大一部分。有什么想法吗?

有什么建议吗?

【问题讨论】:

    标签: grails groovy heap-memory quartz-scheduler


    【解决方案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 中似乎存在内存泄漏,但可以通过重用连接来规避它。

    希望这可以帮助某人节省一些时间。

    【讨论】:

      猜你喜欢
      • 2014-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-19
      • 1970-01-01
      • 2012-03-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多