I've managed to wire the AnonymousClassLoader into JRuby. A patch is
attached, that will probably be rather confusing to anyone unfamiliar
with JRuby.
ClassCache is basically a smart class-loading utility that can be used
to share loaded classes across JRuby instances without keeping hard
references to them and preventing them from unloading.
AnonymousClassLoader is an all-static utility class that wraps the logic
of instantiating "one-shot" classloaders for loading bodies of code. In
this case, it either uses java.dyn.AnonymousClassLoader or our own
"OneShotClassLoader" which is not anonymous but which is used for only a
single class.
The code generally appears to work about the same with
java.dyn.AnonymousClassLoader available, which is good. But I'm having
trouble quantifying the benefit to JRuby. I'd like to be able to show
how it helps, but memory profiles look practically the same between the
version using an OneShotClassLoader per method body and the version
using AnonymousClassLoader per method body. I have not done a lot of
work to isolate the cost of loading, but it seems to be unnoticeable in
my simple benchmark which defines 100_000 Ruby methods and forces them
to JIT.
So the runtime benefits may not be so great. The practical benefits,
such as being able to chuck byte[] into AnonymousClassLoader without
decorating class names and ensuring uniqueness, I have not yet utilized
in this code. Those benefits may show how writing class caches like the
one in JRuby are made a lot easier. For the moment, in order to allow
JRuby to support both MLVM work and pre-JDK7 JVMs, this patch still does
name-mangling to ensure methods are unique.
I think part of my confusion is that originally I desired a class loader
for which I could have a single instance I would throw *many* byte[] at,
and they'd all be loaded using that classloader but without hard
references and without the overhead of a classloader-per-method. But the
AnonymousClassLoader interface appears to one exactly one byte[] per
instance, though it does have considerably reduced in-memory cost per
AnonymousClassLoader instance.
Basically, what I've been looking for to make my life easier and memory
costs lower is:
ClassLoader cl = someClassLoader....;
Class first = cl.defineClass(firstClass);
Class second = cl.defineClass(secondClass);
first = null; // and at some point I would expect the class to GC
Is this the purpose of AnonymousClassLoader? Am I doing it wrong? I'll
be poking around for example code now.
- Charlie
Index: src/org/jruby/runtime/MethodFactory.java
===================================================================
--- src/org/jruby/runtime/MethodFactory.java (revision 6575)
+++ src/org/jruby/runtime/MethodFactory.java (working copy)
@@ -74,13 +74,33 @@
* @param classLoader The classloader to use for searching for and
* dynamically loading code.
* @return A new MethodFactory.
+ * @deprecated Not compatible with DVM/JDK7 AnonymousClassLoader
*/
public static MethodFactory createFactory(ClassLoader classLoader) {
if (reflection) return new ReflectionMethodFactory();
- if (dumping) return new DumpingInvocationMethodFactory(dumpingPath,
classLoader);
return new InvocationMethodFactory(classLoader);
}
+
+ /**
+ * Based on optional properties, create a new MethodFactory. By default,
+ * this will create a code-generation-based InvocationMethodFactory. If
+ * security restricts code generation, ReflectionMethodFactory will be
used.
+ * If we are dumping class definitions, DumpingInvocationMethodFactory will
+ * be used. See MethodFactory's static initializer for more details.
+ *
+ * This version
+ *
+ * @param hostClass The class to use as "host" for the new invoker, or the
+ * class from which to get a classloader to use for searching for and
+ * dynamically loading code.
+ * @return A new MethodFactory.
+ */
+ public static MethodFactory createFactory(Class hostClass) {
+ if (reflection) return new ReflectionMethodFactory();
+
+ return new InvocationMethodFactory(hostClass);
+ }
/**
* Get a new method handle based on the target JRuby-compiled method.
Index: src/org/jruby/internal/runtime/methods/InvocationMethodFactory.java
===================================================================
--- src/org/jruby/internal/runtime/methods/InvocationMethodFactory.java
(revision 6575)
+++ src/org/jruby/internal/runtime/methods/InvocationMethodFactory.java
(working copy)
@@ -54,6 +54,7 @@
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.AnonymousClassLoader;
import org.jruby.util.CodegenUtils;
import static org.jruby.util.CodegenUtils.*;
import static java.lang.System.*;
@@ -152,8 +153,8 @@
/** The lvar index of the passed-in Block on the call */
public static final int BLOCK_INDEX = 6;
- /** The classloader to use for code loading */
- protected JRubyClassLoader classLoader;
+ /** The class to use as host for anonymous classloading */
+ protected Class hostClass;
/**
* Whether this factory has seen undefined methods already. This is used to
@@ -169,14 +170,26 @@
*
* @param classLoader The classloader to use, or to wrap if it is not a
* JRubyClassLoader instance.
+ * @deprecated Do not use this version anymore, since it is not compatible
+ * with DVM/JDK7 anonymous classloading.
*/
public InvocationMethodFactory(ClassLoader classLoader) {
- if (classLoader instanceof JRubyClassLoader) {
- this.classLoader = (JRubyClassLoader)classLoader;
- } else {
- this.classLoader = new JRubyClassLoader(classLoader);
- }
+ hostClass = InvocationMethodFactory.class;
}
+
+ /**
+ * Construct a new InvocationMethodFactory using the class to host loaded
+ * code. Created classes are either constructed using the DVM/JDK7
+ * AnonymousClassLoader or using a "one shot" classloader for all
+ * construction.
+ *
+ * @param classLoader The classloader to use, or to wrap if it is not a
+ * JRubyClassLoader instance.
+ * @see AnonymousClassLoader
+ */
+ public InvocationMethodFactory(Class hostClass) {
+ this.hostClass = hostClass;
+ }
/**
* Use code generation to provide a method handle for a compiled Ruby
method.
@@ -189,7 +202,7 @@
String sup = COMPILED_SUPER_CLASS;
Class scriptClass = scriptObject.getClass();
String mname = scriptClass.getName() + "Invoker" + method + arity;
- synchronized (classLoader) {
+ synchronized (hostClass) {
Class generatedClass = tryClass(mname);
try {
@@ -291,10 +304,7 @@
if (DEBUG) out.println("Binding multiple: " + desc1.declaringClassName
+ "." + javaMethodName);
- String generatedClassName =
CodegenUtils.getAnnotatedBindingClassName(javaMethodName,
desc1.declaringClassName, desc1.isStatic, desc1.actualRequired, desc1.optional,
true);
- String generatedClassPath = generatedClassName.replace('.', '/');
-
- synchronized (classLoader) {
+ synchronized (hostClass) {
try {
Class c = getAnnotatedMethodClass(descs);
@@ -366,7 +376,7 @@
String generatedClassName =
CodegenUtils.getAnnotatedBindingClassName(javaMethodName,
desc1.declaringClassName, desc1.isStatic, desc1.actualRequired, desc1.optional,
true);
String generatedClassPath = generatedClassName.replace('.', '/');
- synchronized (classLoader) {
+ synchronized (hostClass) {
Class c = tryClass(generatedClassName);
int min = Integer.MAX_VALUE;
@@ -485,10 +495,7 @@
public DynamicMethod getAnnotatedMethod(RubyModule implementationClass,
JavaMethodDescriptor desc) {
String javaMethodName = desc.name;
- String generatedClassName =
CodegenUtils.getAnnotatedBindingClassName(javaMethodName,
desc.declaringClassName, desc.isStatic, desc.actualRequired, desc.optional,
false);
- String generatedClassPath = generatedClassName.replace('.', '/');
-
- synchronized (classLoader) {
+ synchronized (hostClass) {
try {
Class c = getAnnotatedMethodClass(desc);
@@ -518,7 +525,7 @@
String generatedClassName =
CodegenUtils.getAnnotatedBindingClassName(javaMethodName,
desc.declaringClassName, desc.isStatic, desc.actualRequired, desc.optional,
false);
String generatedClassPath = generatedClassName.replace('.', '/');
- synchronized (classLoader) {
+ synchronized (hostClass) {
Class c = tryClass(generatedClassName);
if (c == null) {
@@ -586,7 +593,7 @@
String generatedClassName = type.getName() + "Invoker";
String generatedClassPath = typePath + "Invoker";
- synchronized (classLoader) {
+ synchronized (hostClass) {
Class c = tryClass(generatedClassName);
try {
@@ -990,10 +997,10 @@
private Class tryClass(String name) {
try {
Class c = null;
- if (classLoader == null) {
- c = Class.forName(name, true, classLoader);
+ if (hostClass == null) {
+ c = Class.forName(name, true, null);
} else {
- c = classLoader.loadClass(name);
+ c = hostClass.getClassLoader().loadClass(name);
}
if (c != null && seenUndefinedClasses) {
@@ -1008,7 +1015,7 @@
}
}
- protected Class endCall(ClassWriter cw, MethodVisitor mv, String name) {
+ protected Class endCall(ClassWriter cw, MethodVisitor mv, String name)
throws ClassNotFoundException {
endMethod(mv);
return endClass(cw, name);
}
@@ -1018,12 +1025,12 @@
mv.visitEnd();
}
- protected Class endClass(ClassWriter cw, String name) {
+ protected Class endClass(ClassWriter cw, String name) throws
ClassNotFoundException {
cw.visitEnd();
byte[] code = cw.toByteArray();
CheckClassAdapter.verify(new ClassReader(code), false, new
PrintWriter(System.err));
- return classLoader.defineClass(name, code);
+ return AnonymousClassLoader.loadClass(name, code, hostClass,
JRubyClassLoader.class.getProtectionDomain());
}
private void loadArgument(MethodVisitor mv, int argsIndex, int argIndex) {
Index:
src/org/jruby/internal/runtime/methods/DumpingInvocationMethodFactory.java
===================================================================
--- src/org/jruby/internal/runtime/methods/DumpingInvocationMethodFactory.java
(revision 6575)
+++ src/org/jruby/internal/runtime/methods/DumpingInvocationMethodFactory.java
(working copy)
@@ -30,7 +30,6 @@
import java.io.File;
import java.io.FileOutputStream;
-import org.jruby.Ruby;
import org.objectweb.asm.ClassWriter;
/**
@@ -44,10 +43,16 @@
private String dumpPath;
+ @Deprecated
public DumpingInvocationMethodFactory(String path, ClassLoader
classLoader) {
super(classLoader);
this.dumpPath = path;
}
+
+ public DumpingInvocationMethodFactory(String path, Class hostClass) {
+ super(hostClass);
+ this.dumpPath = path;
+ }
@Override
protected Class endClass(ClassWriter cw, String name) {
@@ -62,6 +67,6 @@
fos.close();
} catch(Exception e) {
}
- return classLoader.defineClass(name, code);
+ return null; //classLoader.defineClass(name, code);
}
}// DumpingInvocationMethodFactory
Index: src/org/jruby/javasupport/util/RuntimeHelpers.java
===================================================================
--- src/org/jruby/javasupport/util/RuntimeHelpers.java (revision 6575)
+++ src/org/jruby/javasupport/util/RuntimeHelpers.java (working copy)
@@ -134,7 +134,7 @@
scope.determineModule();
scope.setArities(required, optional, rest);
- MethodFactory factory =
MethodFactory.createFactory(compiledClass.getClassLoader());
+ MethodFactory factory = MethodFactory.createFactory(compiledClass);
DynamicMethod method;
if (name.equals("initialize") || visibility ==
Visibility.MODULE_FUNCTION) {
@@ -191,7 +191,7 @@
scope.determineModule();
scope.setArities(required, optional, rest);
- MethodFactory factory =
MethodFactory.createFactory(compiledClass.getClassLoader());
+ MethodFactory factory = MethodFactory.createFactory(compiledClass);
DynamicMethod method;
method = factory.getCompiledMethod(rubyClass, javaName,
Index: src/org/jruby/RubyModule.java
===================================================================
--- src/org/jruby/RubyModule.java (revision 6575)
+++ src/org/jruby/RubyModule.java (working copy)
@@ -462,7 +462,7 @@
// FIXME: This is probably not very efficient, since it loads all
methods for each call
boolean foundMethod = false;
for (Method method : clazz.getDeclaredMethods()) {
- if (method.getName().equals(name) && defineAnnotatedMethod(method,
MethodFactory.createFactory(getRuntime().getJRubyClassLoader()))) {
+ if (method.getName().equals(name) && defineAnnotatedMethod(method,
MethodFactory.createFactory(Ruby.class))) {
foundMethod = true;
}
}
@@ -605,7 +605,7 @@
} catch (Throwable t) {
if (DEBUG) System.out.println("Could not find it!");
// fallback on non-pregenerated logic
- MethodFactory methodFactory =
MethodFactory.createFactory(getRuntime().getJRubyClassLoader());
+ MethodFactory methodFactory =
MethodFactory.createFactory(Ruby.class);
MethodClumper clumper = new MethodClumper();
clumper.clump(clazz);
@@ -638,7 +638,7 @@
}
private void defineAnnotatedMethodsIndexed(Class clazz) {
- MethodFactory methodFactory =
MethodFactory.createFactory(getRuntime().getJRubyClassLoader());
+ MethodFactory methodFactory = MethodFactory.createFactory(Ruby.class);
methodFactory.defineIndexedAnnotatedMethods(this, clazz,
methodDefiningCallback);
}
Index: src/org/jruby/util/ClassCache.java
===================================================================
--- src/org/jruby/util/ClassCache.java (revision 6575)
+++ src/org/jruby/util/ClassCache.java (working copy)
@@ -2,8 +2,6 @@
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
-import java.net.URL;
-import java.net.URLClassLoader;
import java.security.ProtectionDomain;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -13,6 +11,13 @@
* multiple runtimes (or whole JVM).
*/
public class ClassCache<T> {
+ private final ReferenceQueue referenceQueue = new ReferenceQueue();
+ private final Map<Object, KeyedClassReference> cache =
+ new ConcurrentHashMap<Object, KeyedClassReference>();
+ private final ClassLoader classLoader;
+ private final int max;
+ private final ProtectionDomain defaultDomain;
+
/**
* The ClassLoader this class cache will use for any classes generated
through it. It is
* assumed that the classloader provided will be a parent loader of any
runtime using it.
@@ -21,6 +26,7 @@
public ClassCache(ClassLoader classLoader, int max) {
this.classLoader = classLoader;
this.max = max;
+ this.defaultDomain = classLoader.getClass().getProtectionDomain();
}
public ClassCache(ClassLoader classLoader) {
@@ -46,34 +52,6 @@
}
}
- private static class OneShotClassLoader extends URLClassLoader {
- private final static ProtectionDomain DEFAULT_DOMAIN =
- JRubyClassLoader.class.getProtectionDomain();
-
- public OneShotClassLoader(ClassLoader parent) {
- super(new URL[0], parent);
- }
-
- // Change visibility so others can see it
- public void addURL(URL url) {
- super.addURL(url);
- }
-
- public Class<?> defineClass(String name, byte[] bytes) {
- return super.defineClass(name, bytes, 0, bytes.length,
DEFAULT_DOMAIN);
- }
-
- public Class<?> defineClass(String name, byte[] bytes,
ProtectionDomain domain) {
- return super.defineClass(name, bytes, 0, bytes.length, domain);
- }
- }
-
- private ReferenceQueue referenceQueue = new ReferenceQueue();
- private Map<Object, KeyedClassReference> cache =
- new ConcurrentHashMap<Object, KeyedClassReference>();
- private ClassLoader classLoader;
- private int max;
-
public ClassLoader getClassLoader() {
return classLoader;
}
@@ -91,8 +69,7 @@
if (weakRef == null || contents == null) {
if (isFull()) return null;
- OneShotClassLoader oneShotCL = new
OneShotClassLoader(getClassLoader());
- contents = (Class<T>)oneShotCL.defineClass(classGenerator.name(),
classGenerator.bytecode());
+ contents =
(Class<T>)AnonymousClassLoader.loadClass(classGenerator.name(),
classGenerator.bytecode(), ClassCache.class, defaultDomain);
cache.put(key, new KeyedClassReference(key, contents,
referenceQueue));
}
Index: src/org/jruby/util/AnonymousClassLoader.java
===================================================================
--- src/org/jruby/util/AnonymousClassLoader.java (revision 0)
+++ src/org/jruby/util/AnonymousClassLoader.java (revision 0)
@@ -0,0 +1,84 @@
+package org.jruby.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.ProtectionDomain;
+
+public class AnonymousClassLoader {
+ private static final boolean DEBUG = true;
+
+ private static final Constructor ANON_CL_CONSTRUCTOR;
+ private static final Method ANON_CL_LOADCLASS;
+ private static final boolean ANONYMOUS_SUPPORTED;
+
+ static {
+ Exception exception = null;
+
+ Class cls = null;
+ Constructor constructor = null;
+ Method method = null;
+
+ try {
+ cls = Class.forName("java.dyn.AnonymousClassLoader");
+ constructor = cls.getConstructor(Class.class, byte[].class);
+ method = cls.getMethod("loadClass");
+ } catch (Exception e) {
+ exception = e;
+ }
+
+ if (method == null && DEBUG) {
+ System.err.println("Could not load AnonymousClassLoader:");
+ exception.printStackTrace();
+ ANON_CL_CONSTRUCTOR = null;
+ ANON_CL_LOADCLASS = null;
+ ANONYMOUS_SUPPORTED = false;
+ } else {
+ ANON_CL_CONSTRUCTOR = constructor;
+ ANON_CL_LOADCLASS = method;
+ ANONYMOUS_SUPPORTED = false;
+ }
+ }
+
+ public static Class loadClass(String name, byte[] bytecode, Class
hostClass, ProtectionDomain domain) throws ClassNotFoundException {
+ Class result;
+
+ if (ANONYMOUS_SUPPORTED) {
+ try {
+ Object anonCL = ANON_CL_CONSTRUCTOR.newInstance(hostClass,
bytecode);
+ result = (Class)ANON_CL_LOADCLASS.invoke(anonCL);
+ } catch (Exception e) {
+ throw new ClassNotFoundException("Could not instantiate
AnonymousClassLoader", e);
+ }
+ } else {
+ OneShotClassLoader oneShotCL = new
OneShotClassLoader(hostClass.getClassLoader(), domain);
+ result = (Class)oneShotCL.defineClass(name, bytecode);
+ }
+
+ return result;
+ }
+
+ private static class OneShotClassLoader extends URLClassLoader {
+ private final ProtectionDomain defaultDomain;
+
+ public OneShotClassLoader(ClassLoader parent, ProtectionDomain domain)
{
+ super(new URL[0], parent);
+ defaultDomain = domain;
+ }
+
+ // Change visibility so others can see it
+ public void addURL(URL url) {
+ super.addURL(url);
+ }
+
+ public Class<?> defineClass(String name, byte[] bytes) {
+ return super.defineClass(name, bytes, 0, bytes.length,
defaultDomain);
+ }
+
+ public Class<?> defineClass(String name, byte[] bytes,
ProtectionDomain domain) {
+ return super.defineClass(name, bytes, 0, bytes.length, domain);
+ }
+ }
+}
+
Index: src/org/jruby/anno/InvokerGenerator.java
===================================================================
--- src/org/jruby/anno/InvokerGenerator.java (revision 6575)
+++ src/org/jruby/anno/InvokerGenerator.java (working copy)
@@ -7,7 +7,6 @@
import java.util.Map;
import org.jruby.RubyModule.MethodClumper;
import org.jruby.internal.runtime.methods.DumpingInvocationMethodFactory;
-import org.jruby.util.JRubyClassLoader;
public class InvokerGenerator {
public static final boolean DEBUG = false;
@@ -26,7 +25,7 @@
br.close();
}
- DumpingInvocationMethodFactory dumper = new
DumpingInvocationMethodFactory(args[1], new
JRubyClassLoader(ClassLoader.getSystemClassLoader()));
+ DumpingInvocationMethodFactory dumper = new
DumpingInvocationMethodFactory(args[1], InvokerGenerator.class);
for (String name : classNames) {
MethodClumper clumper = new MethodClumper();
_______________________________________________
mlvm-dev mailing list
[email protected]
http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev