6

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

操作系统:Ubuntu Linux 2.6.24-24-server #1 SMP x86_64 GNU/Linux Java:1.6.0_21 Tomcat:6.0.28 JRuby:1.5.0 Rails:2.3.7

我们目前正在被谷歌、雅虎和百度抓取,因此网站使用率上升。我一直在使用 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
4

4 回答 4

6

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

我可以提供几个选项:

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

编辑:仅供参考,这个问题原来是一个 JRuby 错误。1.5.2 和 1.6 版本应该解决这个特定问题。我上面的评论仍然普遍存在。

于 2010-07-16T17:48:44.093 回答
2

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

蛮力建议:

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


编辑

哦那好吧!无聊的解决方案

于 2010-07-16T16:26:41.743 回答
2

我们在使用 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 对象的地方的代码。

于 2010-07-29T23:17:55.840 回答
2

只是为了提供一个简单的例子来展示这个问题和解决方法:

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。

于 2010-07-30T13:57:55.740 回答