在 Tomcat 中使用 JRuby on Rails 追踪 PermGen 问题
我们正在运行一个在 Tomcat 下运行的 JRuby on Rails 编写的小型 Web 应用程序。我们使用与另一个生产 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 Rails:2.3.7
我们目前正在被 Google、雅虎和百度抓取,因此网站使用率有所上升。我一直在使用 JConsole 监控 Tomcat,我们确实发现了类数量过多的问题。当 tomcat 启动时,我们加载了大约 12,000 个类。 8 小时后,我们加载了近 75,000 个类。 PermGen 同时从 100MB 增加到 460MB。
类卸载正在运行,但在同一 8 小时内仅卸载了约 500 个类。 PermGen 似乎永远不会被收集。
我们正在使用以下 Tomcat 虚拟机选项运行:
-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.DelegatingClassLoader、org.jruby.util.JRubyClassLoader 或 org.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
We're running a small web application written JRuby on Rails running under Tomcat. We're using a Spring back-end that's shared with another production web application. Unfortunately, we keep running into PermGen problems.
OS: 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
We're currently getting crawled by Google, Yahoo and Baidu, so site usage is up. I've been monitoring Tomcat with JConsole and we're definitely seeing a problem with an excessive number of classes. When tomcat launches, we have about 12,000 classes loaded. After 8 hours, we have almost 75,000 classes loaded. PermGen goes from 100MB to 460MB in the same time.
Class unloading is working, but it only unloaded ~500 classes over that same 8 hour period. PermGen never seems to get collected.
We're running with the following VM options for Tomcat:
-Xms2048m -Xmx2048m -XX:MaxPermSize=512m -XX:PermSize=128m \
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:ParallelGCThreads=4 \
-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled
Obviously there's some kind of leak. The question is how where? Any advice on how to track down who and what is responsible for this? I'm hoping it's some really silly mistake on our part, but I'm not sure where to begin.
Any advice would be greatly appreciated.
EDIT
It looks like we're seeing one new class created for every single incoming request.
EDIT 2
It's definitely related to JRuby. Using JConsole, I enabled Verbose mode for the class loader. Here's a sample from 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]
So the question becomes how do I track down the party responsible for creating those extra classes?
EDIT 3
Not sure if this the problem, but somehow we're ending up with an insane number of class loaders. Ran jmap -permstat PID
and got:
class_loader classes bytes parent_loader alive? type
total = 1320 135748 947431296 N/A alive=1, dead=1319 N/A
That seems a little excessive. The majority are one of three kinds of classloaders: sun.reflect.DelegatingClassLoader
, org.jruby.util.JRubyClassLoader
or org.jruby.util.ClassCache$OneShotClassLoader
. Again, sample output from 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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
只是提供一个简单的示例来展示此问题和解决方法:
假设文件名为 test_classload.rb,则输出如下:
$ jruby -J-XX:+TraceClassLoading test_classload.rb | grep anon_class
[从 JVM_DefineClass 加载 anon_class819349464_307995535]
[从 JVM_DefineClass 加载 anon_class729155693_307995574]
[Loaded anon_class1690464956_307995577 from JVM_DefineClass]
如果切换到注释行,输出为空:没有加载 anon_class。
Just to provide a simple example to show this problem and workaround:
Assume the file name is test_classload.rb, the following are outputs:
$ jruby -J-XX:+TraceClassLoading test_classload.rb | grep anon_class
[Loaded anon_class819349464_307995535 from JVM_DefineClass]
[Loaded anon_class729155693_307995574 from JVM_DefineClass]
[Loaded anon_class1690464956_307995577 from JVM_DefineClass]
If switch to the commented line, the output is empty: no anon_class loaded.
我们在使用 JRuby 1.5.1 的 Sinatra Web 应用程序中遇到了类似的问题:
JVM TraceClassLoading 选项打印出随每个请求一起加载的 anon_class*。
在花了一些时间来缩小匿名类的加载位置(这是通过将跟踪语句打印到控制台来完成的)之后,我们最终发现这是由于调用 Java 对象上缺少的方法引起的。
该调用触发 JRuby 将缺少的方法添加到 Java 对象中。该过程创建了一个新的单例 JRuby 类,该类被命名为“anon_class”,后跟一些哈希值。由于它是一个类类型,因此它保留在 PermGen 中并且永远不会被 GC 收集。
解决方法是避免调用缺少的方法或提供实现。在我们的例子中,我们尝试使用 Java ArrayList 对象上的块调用排序方法。如果我们先调用“to_a”方法将Java ArrayList转换为JRuby数组,那么,使用块排序将不会创建anon_class。
因此,我建议检查从 JRudy 访问 Java 对象的位置的代码。
We had similar problem with a Sinatra web application using JRuby 1.5.1:
JVM TraceClassLoading option prints out anon_class* loaded along with every request.
After spending sometime to narrow down where that anonymous class got loaded, which is done by printing out trace statements to console, we finally figured out it was caused by calling a missing method on a Java object.
That call triggered JRuby to add that missing method to the Java object. That process created a new singleton JRuby class, which was named "anon_class" followed by some hash values. Since it is a class type, it stays in PermGen and never gets collected by GC.
The workaround is to avoid calling that missing method or provide a implementation. In our case, we were trying to call sort method with a block on a Java ArrayList object. If we call the "to_a" method first to convert Java ArrayList to JRuby array, then, sort with a block will not create an anon_class.
So, I would suggest to review code for places accessing Java object from JRudy.
有分析工具,也有知道如何使用它们的人。恐怕我不是他们中的一员。
强力建议:
每 8 小时重新启动一次 Tomcat。用户所看到的总停机时间是可以接受的。问题解决了;)
编辑
哦,好吧! 无聊的解决方案。
There are profiling tools, and people who know how to use them. I'm not one of them, I'm afraid.
Brute force advice:
Restart your Tomcat every 8 hours. Total downtime as seen by your users will be very acceptable. Problem solved ;)
EDIT
Oh, all right! The boring solution.
PermGen 绝对是基于 JRuby 的应用程序的一个问题。对于 CMS 收集的不多,我并不感到惊讶。通常情况下,并不存在真正的内存泄漏,而是应用程序在永久生成上非常繁重且困难,并且尚未稳定下来。
我可以提供一些选择:
threadsafe!
模式运行。在单个运行时中运行应用程序是节省大量内存的另一种方法,这也适用于 permgen。编辑:仅供参考,这个问题原来是JRuby bug。 1.5.2 和 1.6 版本应该修复这个特定问题。我上面的评论仍然是普遍的。
PermGen is definitely a problem with JRuby-based applications. I'm not surprised that the CMS is not collecting much. Typically there isn't a true memory leak, but rather the application is just heavy and hard on permgen and hasn't leveled off yet.
I can offer a few options:
threadsafe!
mode. Running your application in a single runtime is another way to gain big memory savings, and that applies to permgen as well.EDIT: FYI, this question turned out to be a JRuby bug. The 1.5.2 and 1.6 releases should fix this particular issue. My comments above still stand in general.