【问题标题】:Track down PermGen problem with JRuby on Rails in Tomcat在 Tomcat 中使用 JRuby on Rails 追踪 PermGen 问题
【发布时间】:2011-03-17 01:35:10
【问题描述】:

我们正在运行一个在 Tomcat 下运行的小型 Web 应用程序,该应用程序编写为 JRuby on Rails。我们正在使用与另一个生产 Web 应用程序共享的 Spring 后端。不幸的是,我们不断遇到 PermGen 问题。

操作系统:Ubuntu Linux 2.6.24-24-server #1 SMP x86_64 GNU/Linux 爪哇:1.6.0_21 雄猫:6.0.28 JRuby:1.5.0 导轨:2.3.7

我们目前正在被 Google、Yahoo 和百度抓取,因此网站使用率正在上升。我一直在使用 JConsole 监控 Tomcat,我们肯定看到了过多类的问题。当 tomcat 启动时,我们加载了大约 12,000 个类。 8 小时后,我们加载了近 75,000 个课程。 PermGen 同时从 100MB 增加到 460MB。

类卸载正在运行,但在同一 8 小时内仅卸载了约 500 个类。 PermGen 似乎永远不会被收集。

我们正在运行 Tomcat 的以下 VM 选项:

-Xms2048m -Xmx2048m -XX:MaxPermSize=512m -XX:PermSize=128m \
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:ParallelGCThreads=4 \
-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

显然存在某种泄漏。问题是如何在哪里?关于如何追查谁和什么对此负责的任何建议?我希望这是我们的一些非常愚蠢的错误,但我不知道从哪里开始。

任何建议将不胜感激。

编辑

看起来我们正在为每个传入的请求创建一个新类。

编辑 2

这肯定与 JRuby 有关。使用 JConsole,我为类加载器启用了详细模式。这是来自 catalina.out 的示例:

[Loaded anon_class1275113147_895127379 from file:/opt/apache-tomcat-6.0.28/webapps/notes/WEB-INF/lib/jruby-core-1.5.0.jar]
[Loaded anon_class1354333392_895127376 from file:/opt/apache-tomcat-6.0.28/webapps/notes/WEB-INF/lib/jruby-core-1.5.0.jar]
[Loaded anon_class1402528430_895127373 from file:/opt/apache-tomcat-6.0.28/webapps/notes/WEB-INF/lib/jruby-core-1.5.0.jar]

所以问题变成了我如何追踪负责创建这些额外类的一方?

编辑 3

不确定这是否是问题所在,但不知何故,我们最终得到了大量的类加载器。跑jmap -permstat PID得到:

class_loader  classes bytes       parent_loader   alive?              type
total = 1320  135748  947431296   N/A             alive=1, dead=1319  N/A

这似乎有点过分了。大多数是以下三种类加载器之一:sun.reflect.DelegatingClassLoaderorg.jruby.util.JRubyClassLoaderorg.jruby.util.ClassCache$OneShotClassLoader。同样,jmap -permstat 的示例输出:

class_loader            classes bytes      parent_loader           alive?  type
0x00007f71f4e93d58      1       3128       0x00007f71f4d54680      dead    sun/reflect/DelegatingClassLoader@0x00007f72ef9a6dc0
0x00007f721e51e2a0      57103   316038936  0x00007f720431c958      dead    org/jruby/util/JRubyClassLoader@0x00007f72f2fd1158
0x00007f72182f2b10      4       12944      0x00007f721d7f3030      dead    org/jruby/util/JRubyClassLoader@0x00007f72f2fd1158
0x00007f721d7d50d8      9       457520     0x00007f720431c958      dead    org/jruby/util/ClassCache$OneShotClassLoader@0x00007f72f3ce2368

【问题讨论】:

    标签: tomcat memory-leaks jruby jrubyonrails permgen


    【解决方案1】:

    PermGen 绝对是基于 JRuby 的应用程序的一个问题。我对 CMS 没有收集太多内容并不感到惊讶。通常不存在真正的内存泄漏,而是应用程序在 permgen 上很繁重,而且还没有稳定下来。

    我可以提供几个选择:

    1. 进一步提高 permgen 以查看是否可以找到平衡点。
    2. 看看是否可以在纯解释模式下运行应用程序 (-Djruby.compile.mode=OFF)。这应该可以摆脱占满 permgen 的大部分类。
    3. 尝试在 Rails 2.2 及更高版本threadsafe! 模式下运行。在单个运行时运行应用程序是另一种节省大量内存的方法,这也适用于 permgen。

    编辑:仅供参考,这个问题原来是a JRuby bug。 1.5.2 和 1.6 版本应该解决这个特定问题。我上面的cmets还是一般的。

    【讨论】:

    • 4 个实例的 75,000 个类对我来说似乎仍然很多...... :) 但我肯定会做更多的测试。谢谢!
    • 让我担心的是,看起来每个新请求都会创建永远不会发布的新类。这对我来说感觉不对。我理解 JRuby 为什么要创建新类,但我不明白为什么它们不被清理...
    • 如何判断 jruby.compile.mode=OFF 是否正常工作?我在 catalina.sh 的 CATALINA_OPTS 中添加了 -Djruby.compile.mode=OFF,启动了 tomcat 并针对该站点启动了一个网络爬虫。 JConsole 表明总类数和总 permgen 正在稳步上升。
    • 没关系。找到 JITCompiler mbean。绝对显示 0 一切。但仍然看到班级人数和 permgen 不断增加。
    【解决方案2】:

    有分析工具,并且知道如何使用它们的人。恐怕我不是他们中的一员。

    蛮力建议:

    每 8 小时重新启动一次 Tomcat。您的用户看到的总停机时间将是非常可接受的。问题解决了;)


    编辑

    哦,好吧! The boring solution.

    【讨论】:

    • 谢谢,但每 8 小时重新启动一次 Tomcat 不是一个可接受的解决方案。特别是因为我是那个会这样做的人。 :) 这里的重点是解决根本问题。
    • cron 和朋友是发明出来的,所以你不需要手动重新启动。您的 Ruby 代码或 JRuby 的实现或它与 Tomcat 的模糊组合中存在问题,因此它甚至不仅仅是“标准”Java 内存问题。我猜你会在修复之前多次重新启动服务器,因此查看脚本定期重启是明智的。
    • 当然。但我对停止和启动 tomcat 并使其自动化的过程非常满意。我们已经控制住了。但是每 8 小时停机一次,即使只有 30-60 秒,管理层也不能接受。你知道它是怎么回事。 :) 所以我在这里的问题真的是在寻找有关如何跟踪问题根源的建议。
    • 一旦他们听到我的咨询费率,他们就会对每天 3 分钟的停机时间感到高兴! ;) 但是说真的,你的情况很困难。运行“普通”Ruby 服务器可能不是一个糟糕的选择。我可以提供的另一个绝望的想法是让自己在Charles Nutter 的脚下哭泣。没有人比他更了解 JRuby。但是,嘿,您可能首先想阅读我链接到的页面 :)
    • 开个玩笑,我认为它直接解决了您的问题,包括一个免费但功能强大的工具来解决它。希望。
    【解决方案3】:

    我们在使用 JRuby 1.5.1 的 Sinatra Web 应用程序中遇到了类似的问题: JVM TraceClassLoading 选项打印出随每个请求一起加载的 anon_class*。

    在花了一些时间来缩小匿名类的加载位置(通过将跟踪语句打印到控制台来完成)之后,我们最终发现它是由调用 Java 对象上的缺失方法引起的。

    该调用触发 JRuby 将缺少的方法添加到 Java 对象。该过程创建了一个新的单例 JRuby 类,该类被命名为“anon_class”,后跟一些哈希值。因为它是一个类类型,所以它留在 PermGen 中,永远不会被 GC 收集。

    解决方法是避免调用缺少的方法或提供实现。在我们的例子中,我们试图使用 Java ArrayList 对象上的块调用 sort 方法。如果我们先调用“to_a”方法将Java ArrayList 转换为JRuby 数组,那么,分块排序不会创建anon_class。

    所以,我建议查看从 JRudy 访问 Java 对象的地方的代码。

    【讨论】:

      【解决方案4】:

      只是提供一个简单的例子来说明这个问题和解决方法:

      require 'java'
      include_class java.util.ArrayList
      
      list = ArrayList.new
      list << 3
      list << 2
      list << 1
      
      3.times do
        new_list = list.sort { |a, b| a <=> b}
        #new_list = list.to_a.sort { |a, b| a <=> b}
        puts new_list
      end
      

      假设文件名为 test_classload.rb,以下是输出: $ jruby -J-XX:+TraceClassLoading test_classload.rb | grep anon_class
      [从 JVM_DefineClass 加载 anon_class819349464_307995535]
      [从 JVM_DefineClass 加载 anon_class729155693_307995574]
      [从 JVM_DefineClass 加载 anon_class1690464956_307995577]

      如果切换到注释行,则输出为空:未加载任何 anon_class。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-26
        • 2011-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多