【问题标题】:Loading JRuby at runtime and ClassLoader leak在运行时加载 JRuby 和 ClassLoader 泄漏
【发布时间】:2012-03-02 22:24:14
【问题描述】:

我正在尝试在运行时动态加载 JRuby(这样我就可以使用任意 JRuby 安装和版本来执行 Ruby 代码)。我的计划大致是创建一个可以访问jruby.jar 的 ClassLoader,然后使用它来加载必要的 JRuby 运行时等。一切都很好,直到我需要多次执行此操作。如果我销毁第一个 JRuby 运行时,第三个或第四个将导致 OutOfMemory: PermGen 空间。

我已将其简化为一个最小示例。该示例同时使用“直接”API 和JRuby Embed API。 “直接”API 部分被注释掉,但两者都表现出相同的行为:经过几次迭代,PermGen 内存不足。 (使用 JRuby 1.6.7 和 JRuby 1.6.5.1 测试)

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import org.junit.Test;

public class JRubyInstantiationTeardownTest {

    @Test
    public void test() throws Exception {
        for (int i = 0; i < 100; ++i) {
            URL[] urls = new URL[] {
                    new URL("file://path/to/jruby-1.6.7.jar")
            };
            ClassLoader cl = new URLClassLoader(urls, this.getClass().getClassLoader());

            // "Direct" API
            /*
            Class<?> klass = cl.loadClass("org.jruby.Ruby");
            Method newInstance = klass.getMethod("newInstance");
            Method evalScriptlet = klass.getMethod("evalScriptlet", String.class);
            Method tearDown = klass.getMethod("tearDown");

            Object runtime = newInstance.invoke(null);
            System.out.println("have " + runtime);
            evalScriptlet.invoke(runtime, "puts 'hello, world'");
            tearDown.invoke(runtime);
            */

            // JRuby Embed API
            Class<?> scriptingContainerClass = cl.loadClass("org.jruby.embed.ScriptingContainer");
            Method terminate = scriptingContainerClass.getMethod("terminate");
            Method runScriptlet = scriptingContainerClass.getMethod("runScriptlet", String.class);

            Object container = scriptingContainerClass.newInstance();
            System.out.println("have " + container);
            runScriptlet.invoke(container, "puts 'hello, world'");
            terminate.invoke(container);
        }
    }

}

问题:尝试使用 ClassLoader 是否合理?如果是这样,这是 JRuby 中的一个错误,还是我的类加载有问题?

奖励:如果这是 JRuby 中的一个错误,Eclipse 内存分析工具之类的工具如何帮助找到源?我可以打开一个堆转储并查看几个 Ruby 对象(我希望在任何给定时间都不会超过一个),但我不确定如何找出为什么这些对象没有被垃圾收集...

【问题讨论】:

  • 您可能应该将链接添加到您的 JRuby 错误报告作为答案并接受它,因为这确实是一个 JRuby 错误。

标签: java ruby memory-leaks jruby classloader


【解决方案1】:

尝试查看 stackoverflow:loading classes with different classloaders to unload them from the JVM when not needed 和那里的引用。成熟的 Web 容器(如 Tomcat)的来源应该在加载/卸载堆栈的某个地方为您的问题提供答案。

PermGen 存储已加载类(和生成的动态代理)的字节码。当所有对类及其类加载器的引用都被清除时,它应该被 GC 正确压缩。但是您的代码证明了某些东西使您的 JRuby 类保持锁定并且可以从主类加载器访问。它可能是 JRuby 在加载时注册的某种回调映射。

【讨论】:

    【解决方案2】:

    编辑:将此报告为错误:JRUBY-6522,现已修复。

    在 Eclipse 内存分析器中进行挖掘之后,我单击了 URLClassLoader 实例之一上的“GC 路径”。它被org.jruby.RubyEncoding$2 引用,而java.lang.ThreadLocal$ThreadLocalMap$Entry 又被引用了。

    查看该源文件内部,我看到正在创建一个静态 ThreadLocal 变量:RubyEncoding.java:266。 ThreadLocals 可能会永远闲逛,引用我的 ClassLoader 并泄漏内存。

    此代码示例成功:

    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    import org.junit.Test;
    
    public class JRubyInstantiationTeardownTest {
    
        public static int i;
    
        @Test
        public void test() throws Exception {
    
            for (i = 0; i < 100; ++i) {
    
                URL[] urls = new URL[] {
                    new URL("file:///home/pat/jruby-1.6.7/lib/jruby.jar")
                };
    
                final ClassLoader cl = new URLClassLoader(urls, this.getClass().getClassLoader());
    
                final Class<?> rubyClass = cl.loadClass("org.jruby.Ruby");
                final Method newInstance = rubyClass.getMethod("newInstance");
                final Method evalScriptlet = rubyClass.getMethod("evalScriptlet", String.class);
                final Method tearDown = rubyClass.getMethod("tearDown");
    
                // "Direct" API
                Callable<Void> direct = new Callable<Void>() {
                    public Void call() throws Exception {
                        // created inside thread because initialization happens immediately
                        final Object ruby = newInstance.invoke(null);
    
                        System.out.println("" + i + ": " + ruby);
                        evalScriptlet.invoke(ruby, "puts 'hello, world'");
                        tearDown.invoke(ruby);
                        return null;
                    }
                };
    
                // JRuby Embed API
                final Class<?> scriptingContainerClass = cl.loadClass("org.jruby.embed.ScriptingContainer");
                final Method terminate = scriptingContainerClass.getMethod("terminate");
                final Method runScriptlet = scriptingContainerClass.getMethod("runScriptlet", String.class);
    
                // created outside thread because ruby instance not created immediately
                final Object container = scriptingContainerClass.newInstance();
    
                Callable<Void> embed = new Callable<Void>() {
                    public Void call() throws Exception {
    
                        System.out.println(i + ": " + container);
                        runScriptlet.invoke(container, "puts 'hello, world'");
                        terminate.invoke(container);
                        return null;
                    }
                };
    
                // separate thread for each loop iteration so its ThreadLocal vars are discarded
                final ExecutorService executor = Executors.newSingleThreadExecutor();
                executor.submit(direct).get();
                executor.submit(embed).get();
                executor.shutdown();
            }
        }
    
    }
    

    现在我想知道这是否是 JRuby 可接受的行为,或者 JRuby-Rack 在 servlet 容器的上下文中做了什么,其中 servlet 容器管理自己的线程池来处理请求。似乎需要维护一个完全独立的线程池,只在这些线程中执行 Ruby 代码,然后确保它们在 servlet 被取消部署时被销毁......

    这很相关:Tomcat Memory Leak Protection

    另请参阅 JVM 错误报告:Provide reclaimable thread local values without Thread termination

    【讨论】:

      猜你喜欢
      • 2013-12-11
      • 2014-08-05
      • 2012-12-06
      • 1970-01-01
      • 2013-09-19
      • 1970-01-01
      • 2012-02-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多