Редактировать: сообщалось об этом как об ошибке: JRUBY-6522 , теперь исправлено.
После поиска в Eclipse Memory Analyzer я щелкнул "путь к ГХ" на одном изЭкземпляры URLClassLoader.На него ссылалась 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 делает в контексте контейнера сервлета, где контейнер сервлета управляет своим собственным пулом потоков для обработки запросов.Кажется, что нужно поддерживать совершенно отдельный пул потоков, выполнять только код Ruby в этих потоках, а затем гарантировать, что они будут уничтожены, когда сервлет не развернут ...
Это очень важно: Защита от утечки памяти Tomcat
См. Также отчет об ошибке JVM: Предоставление локальных значений для исправляемых потоков без завершения потока