That’s terrific! I’m here in my living room, trying to sort out some of the ideas in my head and putting them in this blog in the hope it would help, and then, someone replies and even proves me wrong! There’s no place to hide one’s ignorance, not even on the web
)
The other day I was trying to clearly describe why I did not like the JVM as a platform for language experimentation, and I got a comment from a reader. Imagine that: a person who read my non-native english (may I say bytecode english?) and took the time to respond. That’s nice. What’s not so nice is that the comment is mostly a rebuttal of my arguments, and the worse part is that the rebuttal is right. My point was that for a dynamic language you needed to generate a new Java class for each new function and that because class data was kept forever (in permgen space, you see permanent generation) there would come a time when the JVM would run out of space. Well, Home: 0 – Visitors: 1. The first part (new class generation) still holds, but the second simply isn’t true. I did some research after the previous post to try to see the light.
The Experiment
My test program contains two classes:
- the first one is the program driver, with a main method and a function that can create new classes on the fly, with almost nothing in them but a constructor and a static function. This is done using the nice ASM library.
- the second one is a simple classloader.
The main function creates 100,000 classes and instantiates each one. There are two options: either keep all instances in an array, or simply discard them. In the first case, the JVM is forced to keep all classes around. In the second case, I thought that the JVM would keep the class data too. And I was wrong, at least for Sun’s JVM 6 on Linux. The complete run in the first case successfully crashes after having filled the permgen space. In the second case, the test completes and jconsole shows a sawtooth graph for memory in the non-heap and permgen space. I will try the same program on other JVMs, just to see if it was always like that and if other ‘vendors’ are as smart as Sun.
The conclusions
The permanent generation is not permanent after all: memory can be reclaimed from there too.
When you write false things in a remote corner of the web because you haven’t done your research, you might well be caught!
What’s next?
In a future post, I’ll try too be less stupid and enumerate irrefutable facts to support my view that the JVM is not the best platform for dynamic language experimentation.
The code
package my.test;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
public class Tester {
// Change this value to force the vm to keep class data around
private static final boolean KEEP_OJBECTS = false;
// Initial sleep time to allow jconsole connection before the real work
private static final int SLEEP_TIME = 20000;
private static final int NB_CLASSES = 100000;
public static class TCL extends ClassLoader {
private Map<String, Class> classes;
public TCL() {
this.classes = new HashMap<String, Class>();
}
public void add(String name, byte[] bytecode) {
this.classes.put(name, bytecode);
}
public Class<?> findClass(String name) {
byte[] b = this.classes.get(name);
return defineClass(name, b, 0, b.length);
}
}
public static void main(String args[]) {
try {
Thread.sleep(SLEEP_TIME);
} catch (Throwable t) {
//
}
String name = "mypack.MyClass";
Object[] objs = new Object[NB_CLASSES];
for (int i = 0; i < NB_CLASSES; ++i) {
String cname = name + i;
Object o = createAndTestClass(cname);
if (KEEP_OJBECTS) {
objs[i] = o;
}
}
}
private static Object createAndTestClass(String name) {
String iname = name.replace('.', '/');
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES
| ClassWriter.COMPUTE_MAXS);
cw.visit(49, Opcodes.ACC_PUBLIC, iname, null, Type
.getInternalName(Object.class), null);
// Constructor
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V",
null, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
"()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 0);
mv.visitEnd();
// method public String callMe() { return "[classname]"; }
mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "callMe",
"()Ljava/lang/String;", null, null);
mv.visitCode();
mv.visitLdcInsn("[" + name + "]");
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(1, 0);
mv.visitEnd();
cw.visitEnd();
TCL cl = new TCL();
cl.add(name, cw.toByteArray());
try {
Class c = cl.loadClass(name);
Method m = c.getMethod("callMe");
String ret = (String) m.invoke(null);
System.out.println(name + ".callMe() -> " + ret);
return c.newInstance();
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
}
0 Responses to “The magic of the web”