Supercharging Reflective Libraries 
with InvokeDynamic 
Ian Robertson 
Myriad Genetics
About Me 
● Application Architect at Myriad Genetics 
● 14 years experience of working on large-scale Java projects 
● Coauthor of Pojomatic – generates toString, equals and 
hashCode 
● Amateur carpenter 
● Blog: http://www.artima.com/weblogs/?blogger=ianr 
● Twitter: @nainostrebor 
● https://github.com/irobertson
Pojomatic 
● Generates equals, hashCode and toString methods 
@Override public toString() { return Pojomatic.toString(this); } 
● Version 1: reflection. 
– Fast, but slower than hand-coded equals 
– Considered bytecode generation, but then we couldn't access private 
fields. 
● Version 2: Bytecode generation with InvokeDynamic 
– Almost an order of magnitude improvement in performance!
Agenda 
● Review reflection 
● Compare reflection to hand-crafted code 
● See how ASM-generated can compete with hand-crafted code 
● See how invokeDynamic can bypass access restrictions 
● Tips and tricks
(Sub)Standard Json 
● Don't bother with quotes 
● Allow for a trailing comma
(Sub)Standard Json 
public interface Jsonifier<T> { 
String marshal(T instance); 
} 
public class Bean { 
@Json("prop1") int i; 
String s; 
@Json("prop2") String getS() { return s; } 
} 
jsonifier.marshal(bean) → 
{ prop1: 3, prop2: hello, }
Old-School Reflection 
p u blic class ReflectionJsonifier<T> 
implements Jsonifier<T> { 
public String marshal(T instance) { 
StringBuilder sb = new StringBuilder(); 
Class<T> = instance.getClass(); 
sb.append(“{”); 
… 
sb.append(“}”); 
return sb.toString(); 
} 
}
Old-School Reflection - fields 
f o r (Field f: clazz.getDeclaredFields()) { 
Json json = f.getAnnotation(Json.class); 
if (json != null) { 
sb.append(json.value() + “: “); 
f.setAccessible(true); 
sb.append(f.get(instance)); 
sb.append(”, ”); 
} 
}
Old-School Reflection - methods 
f o r (Method m: clazz.getDeclaredMethods()) { 
Json json = m.getAnnotation(Json.class); 
if (json != null) { 
sb.append(json.value() + “: “); 
m.setAccessible(true); 
sb.append(m.invoke(instance)); 
sb.append(“, ”); 
} 
}
Misc improvements 
● Do reflection once on marshaler instantiation 
● Reuse marshalers 
● Save the costs of reflection, but not reflective invocation 
● If a field type is primitive, use field.getInt(instance) instead of 
autoboxing field.get(instance);
Hand-coded 
p u blic class BeanJsonifier 
implements Jsonifier<Bean> { 
@Override 
public String marshal(Bean bean) { 
return "{prop1: " + bean.i 
+ ", prop2: " + bean.getS() + “}”; 
} 
}
Benchmarks (ns/call) 
Reflection 
HandRolled 
88 
516
REFLECTION 
Y U SO SLOW?!
Callsite Methods 
● Callsite: foo.bar() at a particular location 
● Monomorphic – One implementation for bar() at the callsite 
– Completely inlinable 
● Bimorphic – Two implementations for bar() at the callsite 
– Inlinable with an instanceof check 
● Megamorphic – 3+ implementations for bar() at the callsite 
– Virtual function call, so no inlining (hence no further optimizations)
Reflection Tends to be Megamorphic 
● A frequently used Field object will eventually generate bytecode 
to do it's get work, placing this in a new class implementing 
sun.reflect.FieldAccessor (Oracle JDK) 
● FieldAccessor.get() will be megamorphic 
– No inlining 
– No optimization 
– No happiness 
● Analogous story for Method.invoke
Bytecode Generation 
● Instead of having the JVM generate bytecode for the individual 
fields and methods, generate the entire marshal(T instance) 
method ourselves at runtime 
● Use ObjectWeb's ASM library to generate bytecode 
● Resulting code necessarily runs as fast as hand-rolled code 
● One small hitch: 
– Who speaks bytecode?
JVM Bytecode 
● Stack based language (not unlike assembly) 
● Local variables (including parameters) 
● Arithmetic 
● Tests, conditional jumps 
● Field access 
● Method invocation 
– Interface, Virtual, Static, “Special”, Dynamic
ASM 
● http://asm.ow2.org/ 
● A Java library for reading and generating Java bytecode at 
runtime 
● Uses the visitor pattern, both for reading and for writing 
bytecode 
● Doesn't make it easy to write bytecode, but does make it 
feasible
ASMifier - ASM for Dummies 
● ASM tool which reads bytecode, generates java code to 
generate bytecode using ASM 
● Command line: 
java ­classpath 
asm­all. 
jar  
org.objectweb.asm.util.ASMifier  
json/Foo.class 
● Eclipse – Bytecode Outline Plugin (http://asm.ow2.org/eclipse/)
ASMifier example 
Convert this: 
package json; 
public class Foo { 
public String bar() { 
return "hello"; 
} 
} 
Into this:
public static byte[] dump() throws Exception { 
ClassWriter cw = new ClassWriter(0); 
FieldVisitor fv; 
MethodVisitor mv; 
AnnotationVisitor av0; 
cw.visit(52, ACC_PUBLIC + ACC_SUPER, "json/Foo", null, 
"java/lang/Object", null); 
cw.visitSource("Foo.java", null); 
{ 
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 
mv.visitCode(); 
mv.visitVarInsn(ALOAD, 0); 
mv.visitMethodInsn( 
INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 
mv.visitInsn(RETURN); 
mv.visitMaxs(1, 1); 
mv.visitEnd(); 
} 
{ 
mv = cw.visitMethod( 
ACC_PUBLIC, "bar", "()Ljava/lang/String;", null, null); 
mv.visitCode(); 
mv.visitLdcInsn("hello"); 
mv.visitInsn(ARETURN); 
mv.visitMaxs(1, 1); 
mv.visitEnd(); 
} 
cw.visitEnd(); 
return cw.toByteArray(); 
} 
}
public static byte[] dump() throws Exception { 
ClassWriter cw = new ClassWriter(0); 
cw.visit(52, ACC_PUBLIC + ACC_SUPER, "json/Foo", 
null, "java/lang/Object", null); 
cw.visitSource("Foo.java", null); 
generateConstructor(cw); 
generateBar(cw); 
cw.visitEnd(); 
return cw.toByteArray(); 
} 
private static void generateConstructor(ClassWriter cw) { 
MethodVisitor mv = cw.visitMethod( 
ACC_PUBLIC, "<init>", "()V", null, null); 
mv.visitCode(); 
mv.visitVarInsn(ALOAD, 0); 
mv.visitMethodInsn( 
INVOKESPECIAL, "java/lang/Object", "<init>", "()V",false); 
mv.visitInsn(RETURN); 
mv.visitMaxs(1, 1); 
mv.visitEnd(); 
} 
private static void generateBar(ClassWriter cw) { 
MethodVisitor mv = cw.visitMethod( 
ACC_PUBLIC, "bar", "()Ljava/lang/String;", null, null); 
mv.visitCode(); 
mv.visitLdcInsn("hello"); 
mv.visitInsn(ARETURN); 
mv.visitMaxs(1, 1); 
mv.visitEnd(); 
} 
}
ASMifier Example – top level 
p u blic static byte[] dump() throws Exception { 
ClassWriter cw = new ClassWriter(0); 
cw.visit(V1_8, ACC_PUBLIC,"json/Foo", 
null, "java/lang/Object", null); 
generateConstructor(cw); 
generateBar(cw); 
cw.visitEnd(); 
return cw.toByteArray(); 
}
ASMifier Example - method 
p r ivate static void generateBar(ClassWriter cw) { 
MethodVisitor mv = cw 
.visitMethod(ACC_PUBLIC, "bar", "()Ljava/lang/String;", null, null); 
mv.visitCode(); 
mv.visitLdcInsn("hello"); 
mv.visitInsn(ARETURN); 
mv.visitMaxs(1, 1); 
mv.visitEnd(); 
}
Converting Bytecode Into a Class 
● Need a ClassLoader! 
public final class ByteClassLoader extends ClassLoader { 
public ByteClassLoader(ClassLoader parent) { 
super(parent); 
} 
Class<?> loadClass(String name, byte[] bytes) { 
return defineClass(name, bytes, 0, bytes.length); 
} 
}
Hello World, Bytecode style 
byte[] bytes = dump(); 
ByteClassLoader cl = new ByteClassLoader( 
getClass().getClassLoader()); 
Class<?> c = cl.loadClass(“json/Foo”, bytes); 
Object foo = c.newInstance(); 
Method m = c.getDeclaredMethod(“bar”); 
Object result = m.invoke(foo); 
System.out.println(result);
Recall 
p u blic class BeanJsonifier 
implements Jsonifier<Bean> { 
@Override 
public String marshal(Bean bean) { 
Return "{prop1: " + bean.i 
+ ", prop2: " + bean.s() + “}”; 
} 
}
Implement an Interface 
cw.visit( 
V1_8, 
ACC_PUBLIC, 
"json/BeanJsonifier", 
"Ljava/lang/Object;” + 
“Ljson/Jsonifier<Ljson/Bean;>;", 
"java/lang/Object", 
new String[] { "json/Jsonifier" } 
);
What we write 
public String marshal(Bean bean) { 
Return "{prop1: " + bean.i 
+ ", prop2: " + bean.s() + “}”; 
}
What the Compiler Sees 
public String marshal(Bean bean) { 
return new StringBuilder("{prop1: ") 
.append(bean.i) 
.append(", prop2: ") 
.append(bean.s()) 
.append(“}”) 
.toString(); 
}
Marshal method – Construct StringBuilder 
mv.visitTypeInsn( 
NEW, "java/lang/StringBuilder"); 
mv.visitInsn(DUP); 
mv.visitLdcInsn("{prop1: "); 
mv.visitMethodInsn(INVOKESPECIAL, 
"java/lang/StringBuilder", "<init>", 
"(Ljava/lang/String;)V", false); 
// note – StringBuilder instance is still 
// on the stack, due to DUP
Marshal method – Construct StringBuilder 
mv.visitTypeInsn( 
NEW, "java/lang/StringBuilder"); 
mv.visitInsn(DUP); 
mv.visitLdcInsn("prop1: "); 
mv.visitMethodInsn(INVOKESPECIAL, 
"java/lang/StringBuilder", "<init>", 
"(Ljava/lang/String;)V", false); 
// note – StringBuilder instance is still 
// on the stack, due to DUP
Marshal method – Construct StringBuilder 
mv.visitTypeInsn( 
NEW, ".../StringBuilder"); 
mv.visitInsn(DUP); 
mv.visitLdcInsn("prop1: "); 
mv.visitMethodInsn(INVOKESPECIAL, 
".../StringBuilder", "<init>", 
"(L.../String;)V", false); 
// note – StringBuilder instance is still 
// on the stack, due to DUP
Marshal method – Construct StringBuilder 
mv.visitTypeInsn(NEW, ".../StringBuilder"); 
mv.visitInsn(DUP); 
mv.visitLdcInsn("prop1: "); 
mv.visitMethodInsn(INVOKESPECIAL, 
".../StringBuilder", "<init>", 
"(L.../String;)V", false); 
// note – StringBuilder instance is still 
// on the stack, due to DUP
Marshal method – Append Field i 
mv.visitVarInsn(ALOAD, 1); // the passed bean 
mv.visitFieldInsn( 
GETFIELD, // what to do 
"json/Bean", // class holding the field 
"i", // field name 
"I"); // field type (integer) 
mv.visitMethodInsn(INVOKEVIRTUAL, 
".../StringBuilder", "append", 
"(I)L.../StringBuilder;", false);
Marshal method – append method getS() 
mv.visitVarInsn(ALOAD, 1); 
mv.visitMethodInsn( 
INVOKEVIRTUAL, // method invocation type 
"json/Bean", // target class 
"get // method name 
"()L.../String;", // method signature 
false); // not an interface 
mv.visitMethodInsn(INVOKEVIRTUAL, 
".../StringBuilder", "append", 
"(L.../String;)L.../StringBuilder;", false);
Marshal method – finish up 
mv.visitMethodInsn( 
INVOKEVIRTUAL, ".../StringBuilder", 
"toString", "()L.../String;", false); 
mv.visitInsn(ARETURN);
Bridge Methods 
● Jsonifier<T> has method 
String marshal(T instance) 
● After erasure, this is equivalent to 
String marshal(Object instance) 
● But we wrote 
String marshal(Bean instance) 
● Compiler generates a bridge method
Marshal Bridge Method 
MethodVisitor mv = cw.visitMethod( 
ACC_PUBLIC | ACC_BRIDGE | ACC_SYNTHETIC, 
"marshal", "(L.../Object;)L.../String;", 
null, null); 
// ... 
mv.visitVarInsn(ALOAD, 0); 
mv.visitVarInsn(ALOAD, 1); 
mv.visitTypeInsn(CHECKCAST, "json/Bean"); 
mv.visitMethodInsn( 
INVOKEVIRTUAL, "json/BeanMarshaller", 
"marshal", "(json/Bean;)L.../String;", false); 
mv.visitInsn(ARETURN);
Marshal Bridge Method 
MethodVisitor mv = cw.visitMethod( 
ACC_PUBLIC | ACC_BRIDGE | ACC_SYNTHETIC, 
"marshal", "(L.../Object;)L.../String;", 
null, null); 
// ... 
mv.visitVarInsn(ALOAD, 0); 
mv.visitVarInsn(ALOAD, 1); 
mv.visitTypeInsn(CHECKCAST, "json/Bean"); 
mv.visitMethodInsn( 
INVOKEVIRTUAL, "json/BeanMarshaller", 
"marshal", "(json/Bean;)L.../String;", false); 
mv.visitInsn(ARETURN);
Constructing an Instance 
ByteClassLoader loader = new ByteClassLoader( 
Bean.class.getClassLoader()); 
byte[] classBytes = dump(); 
Class<?> jsonifierClass = loader.loadClass( 
“json/BeanJsonifier”, classBytes); 
Jsonifier<Bean> jsonifier = (Jsonifier<Bean>) 
jsonifierClass.newInstance(); 
jsonifier.marshal(foo);
Trouble in Paradise 
public class Bean { 
@Json("prop1") 
public int i; 
String s; 
@Json("prop2") 
public String getS() { return s; } 
}
Trouble in Paradise 
public class Bean { 
@Json("prop1") 
private int i; 
String s; 
@Json("prop2") 
private String getS() { return s; } 
}
Trouble in Paradise 
● IllegalAccessError 
– tried to access field json.Bean.i from class json.BeanMarshaller 
● Didn't happen back in the early days (as late as some versions 
of Java 5) 
● Newer Java checks all bytecode for conformance 
● Reflection can call setIsAccessible 
● But reflection is slow!
InvokeDynamic 
● Originally added to support dynamic languages, but has found 
use far beyond that 
– Lambdas use it! 
● First time it's invoked, effectively replaces itself with the 
resulting call 
● Allows for excellent inlining, on par with handwritten code! 
● Not available from java code – only bytecode 
● ASMifier cannot help us here :(
InvokeDynamic flow 
● Class calls invokeDynamic, pointing to a bootstrap method... 
● Which creates a MethodHandle... 
● Which is wrapped in a CallSite... 
● Which henceforth is run in place of the invokeDynamic call
Bootstrap Method 
● Takes arguments of type 
– MethodHandles.Lookup lookup – used for looking up MethodHandles 
– String name – the method name 
– MethodType methodType – the type of method to create 
– Any Bootstrap method constant arguments 
● Returns a CallSite 
– Wrapper around a MethodHandle
MethodHandles 
● Typically constructors, method invocations or field access 
● A MethodHandle instance comes from the Lookup passed into the 
bootstrap method 
● Lookup can see only what the calling class can see... 
● But that includes reflection! 
● Lookup.unreflect converts a java.lang.reflect.Method 
instance 
● Lookup.unreflectGetter converts a j.l.r.Field into a field 
access call site
Call Sites 
● A wrapper around a Method Handle 
● Can be mutable or constant 
● If constant, the JVM can effectively replace the invokeDynamic 
opcode with the wrapped method handle 
– Allows for inlining and subsequent optimizations
Field Access Bootstrap Method 
public static CallSite invokeField( 
MethodHandles.Lookup lookup, 
String name, 
MethodType type, 
Class<?> owner, String fieldName) { 
Field f = owner.getDeclaredField(fieldName); 
f.setAccessible(true); 
MethodHandle mh = lookup.unreflectGetter(f); 
return new ConstantCallSite(mh); 
}
Method Call Bootstrap Method 
public static CallSite invokeMethod( 
MethodHandles.Lookup lookup, 
String name, 
MethodType type, 
Class<?> owner, String methodName) { 
Method m = owner.getDeclaredMethod(methodName); 
m.setAccessible(true); 
MethodHandle mh = lookup.unreflect(m); 
return new ConstantCallSite(mh); 
}
Calling InvokeDynamic 
● The invokeDynamic JVM instruction is called with: 
– Method name (passed to the bootstrap method) 
– Method descriptor for that which will replace the invokeDynamic call 
– Bootstrap method 
– Bootstrap method constant arguments (0 or more) 
● Primitive 
● String 
● Class 
● Method/field reference
Calling invokeDynamic for Field “i” 
mv.visitInvokeDynamicInsn( 
"i", // name for “method” 
"(Ljson/Bean;)I", // signature: int i(Bean) 
new Handle( // method handle 
H_INVOKESTATIC, // static method 
"json/Bootstraps", // class name 
"getField", // bootstrap method 
bootstrapSignature), // signature (TBD) 
"Ljson/Bean;", "i"); // bootstrap const args 
// mv.visitFieldInsn(GETFIELD, "json/Bean", "i", "I");
Calling invokeDynamic for Method “s” 
mv.visitInvokeDynamicInsn( 
"s", 
“(Ljson/Bean;)java/lang/String”, 
new Handle( 
H_INVOKESTATIC, 
"json/Bootstraps", 
"invokeMethod", 
bootstrapSignature), 
“Ljson/Bean;”, "getS"); 
// mv.visitMethodInsn(INVOKE_VIRTUAL, "json/Bean", "s", 
// "()L.../String;", false);
bootstrapSignature 
String bootstrapSignature = MethodType.methodType( 
CallSite.class, 
Lookup.class, 
String.class, 
MethodType.class, 
Class.class, // first bootstrap method argument 
String.class)// second bootstrap method argument 
.toMethodDescriptorString(); 
// “(L.../MethodHandles$Lookup;L...;L.../MethodType;L.../Class;L.../String;)” 
// + “L.../CallSite;”
Still to do 
● Instead of building class for Bean.class, do it dynamically, 
based on (one-time) reflection 
● Left as exercise to reader... 
for (Field f: clazz.getDeclaredFields()) { 
Json json = f.getAnnotation(Json.class); 
if (json != null) { 
generateByteCodeForAnnotatedField(f); 
} 
}
Benchmarks (ns/call) 
Reflection 
HandRolled 
Asm 
88 
84 
516
Benchmarks (ns/call) 
Reflection 
HandRolled 
Asm 
88 
84 
516 
Your millage 
may vary...
Benchmarks (ns/call) 
Reflection 
HandRolled 
Asm 
Asm w/o nulls 
302 
88 
84 
516
Tips and Tricks
CheckClassAdapter 
● Can wrap ASM ClassWriter to catch some errors at bytecode 
generation time instead of at runtime 
● Gives much more meaningful error messages 
● If using COMPUTE_MAXS to ask ASM to compute size for you, 
then first generate bytecode, wrap CheckClassAdapter around 
a ClassReader
Line Numbers 
● cw.visitSource(“Look at JsonifierBytecodeGenerator”) 
● Label label = new Label(); 
mv.visitLabel(label); 
mv.visitLineNumber(200 + fieldNumber) 
● Resulting stack trace can be very useful in diagnosing bugs 
● Also can declare local variables to allow step-in debugging! 
– mv.visitLocalVariable(...)
Bootstrap Method Arguments 
● Many things cannot be passed directly to a bootstrap method 
– Notably – non-public classes 
– Can pass a reference to public class with static Class variables 
holding the class reference 
● The bootstrap method can also be part of the generated class 
– Probably more pain than its worth
SecurityManager 
● Run reflection and bytecode generation via 
AccessController.doPrivileged(PrivilegedAction) 
● Test using java.security.Policy.setPolicy 
– Set policy should override implies(ProtectionDomain,Permission) 
● Make sure to allow your test framework to do stuff! 
– Also call System.setSecurityManager(new SecurityManager());
Testing 
● Any generated bytecode needs extensive unit testing 
● Test what security permissions are required 
● Test different types of classes – public, private, inner, inherited, 
etc 
● Test different types of properties – primitives, Objects, arrays, 
nested arrays, etc 
● Test for synthetic methods 
● Test stack traces for line numbers
Don't Overdo it! 
● Benchmark your code before optimizing at all 
● Write as much code as possible directly in Java 
– Call out from bytecode using invokeVirtual
Thank you! 
● http://www.slideshare.net/nainostrebor/ 
● https://github.com/irobertson/invokedynamic-talk-code 
● Don't forget to fill out surveys!!!

Supercharging reflective libraries with InvokeDynamic

  • 1.
    Supercharging Reflective Libraries with InvokeDynamic Ian Robertson Myriad Genetics
  • 2.
    About Me ●Application Architect at Myriad Genetics ● 14 years experience of working on large-scale Java projects ● Coauthor of Pojomatic – generates toString, equals and hashCode ● Amateur carpenter ● Blog: http://www.artima.com/weblogs/?blogger=ianr ● Twitter: @nainostrebor ● https://github.com/irobertson
  • 3.
    Pojomatic ● Generatesequals, hashCode and toString methods @Override public toString() { return Pojomatic.toString(this); } ● Version 1: reflection. – Fast, but slower than hand-coded equals – Considered bytecode generation, but then we couldn't access private fields. ● Version 2: Bytecode generation with InvokeDynamic – Almost an order of magnitude improvement in performance!
  • 4.
    Agenda ● Reviewreflection ● Compare reflection to hand-crafted code ● See how ASM-generated can compete with hand-crafted code ● See how invokeDynamic can bypass access restrictions ● Tips and tricks
  • 5.
    (Sub)Standard Json ●Don't bother with quotes ● Allow for a trailing comma
  • 6.
    (Sub)Standard Json publicinterface Jsonifier<T> { String marshal(T instance); } public class Bean { @Json("prop1") int i; String s; @Json("prop2") String getS() { return s; } } jsonifier.marshal(bean) → { prop1: 3, prop2: hello, }
  • 7.
    Old-School Reflection pu blic class ReflectionJsonifier<T> implements Jsonifier<T> { public String marshal(T instance) { StringBuilder sb = new StringBuilder(); Class<T> = instance.getClass(); sb.append(“{”); … sb.append(“}”); return sb.toString(); } }
  • 8.
    Old-School Reflection -fields f o r (Field f: clazz.getDeclaredFields()) { Json json = f.getAnnotation(Json.class); if (json != null) { sb.append(json.value() + “: “); f.setAccessible(true); sb.append(f.get(instance)); sb.append(”, ”); } }
  • 9.
    Old-School Reflection -methods f o r (Method m: clazz.getDeclaredMethods()) { Json json = m.getAnnotation(Json.class); if (json != null) { sb.append(json.value() + “: “); m.setAccessible(true); sb.append(m.invoke(instance)); sb.append(“, ”); } }
  • 10.
    Misc improvements ●Do reflection once on marshaler instantiation ● Reuse marshalers ● Save the costs of reflection, but not reflective invocation ● If a field type is primitive, use field.getInt(instance) instead of autoboxing field.get(instance);
  • 11.
    Hand-coded p ublic class BeanJsonifier implements Jsonifier<Bean> { @Override public String marshal(Bean bean) { return "{prop1: " + bean.i + ", prop2: " + bean.getS() + “}”; } }
  • 12.
  • 13.
    REFLECTION Y USO SLOW?!
  • 14.
    Callsite Methods ●Callsite: foo.bar() at a particular location ● Monomorphic – One implementation for bar() at the callsite – Completely inlinable ● Bimorphic – Two implementations for bar() at the callsite – Inlinable with an instanceof check ● Megamorphic – 3+ implementations for bar() at the callsite – Virtual function call, so no inlining (hence no further optimizations)
  • 15.
    Reflection Tends tobe Megamorphic ● A frequently used Field object will eventually generate bytecode to do it's get work, placing this in a new class implementing sun.reflect.FieldAccessor (Oracle JDK) ● FieldAccessor.get() will be megamorphic – No inlining – No optimization – No happiness ● Analogous story for Method.invoke
  • 16.
    Bytecode Generation ●Instead of having the JVM generate bytecode for the individual fields and methods, generate the entire marshal(T instance) method ourselves at runtime ● Use ObjectWeb's ASM library to generate bytecode ● Resulting code necessarily runs as fast as hand-rolled code ● One small hitch: – Who speaks bytecode?
  • 17.
    JVM Bytecode ●Stack based language (not unlike assembly) ● Local variables (including parameters) ● Arithmetic ● Tests, conditional jumps ● Field access ● Method invocation – Interface, Virtual, Static, “Special”, Dynamic
  • 18.
    ASM ● http://asm.ow2.org/ ● A Java library for reading and generating Java bytecode at runtime ● Uses the visitor pattern, both for reading and for writing bytecode ● Doesn't make it easy to write bytecode, but does make it feasible
  • 19.
    ASMifier - ASMfor Dummies ● ASM tool which reads bytecode, generates java code to generate bytecode using ASM ● Command line: java ­classpath asm­all. jar org.objectweb.asm.util.ASMifier json/Foo.class ● Eclipse – Bytecode Outline Plugin (http://asm.ow2.org/eclipse/)
  • 20.
    ASMifier example Convertthis: package json; public class Foo { public String bar() { return "hello"; } } Into this:
  • 21.
    public static byte[]dump() throws Exception { ClassWriter cw = new ClassWriter(0); FieldVisitor fv; MethodVisitor mv; AnnotationVisitor av0; cw.visit(52, ACC_PUBLIC + ACC_SUPER, "json/Foo", null, "java/lang/Object", null); cw.visitSource("Foo.java", null); { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn( INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); } { mv = cw.visitMethod( ACC_PUBLIC, "bar", "()Ljava/lang/String;", null, null); mv.visitCode(); mv.visitLdcInsn("hello"); mv.visitInsn(ARETURN); mv.visitMaxs(1, 1); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
  • 22.
    public static byte[]dump() throws Exception { ClassWriter cw = new ClassWriter(0); cw.visit(52, ACC_PUBLIC + ACC_SUPER, "json/Foo", null, "java/lang/Object", null); cw.visitSource("Foo.java", null); generateConstructor(cw); generateBar(cw); cw.visitEnd(); return cw.toByteArray(); } private static void generateConstructor(ClassWriter cw) { MethodVisitor mv = cw.visitMethod( ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn( INVOKESPECIAL, "java/lang/Object", "<init>", "()V",false); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); } private static void generateBar(ClassWriter cw) { MethodVisitor mv = cw.visitMethod( ACC_PUBLIC, "bar", "()Ljava/lang/String;", null, null); mv.visitCode(); mv.visitLdcInsn("hello"); mv.visitInsn(ARETURN); mv.visitMaxs(1, 1); mv.visitEnd(); } }
  • 23.
    ASMifier Example –top level p u blic static byte[] dump() throws Exception { ClassWriter cw = new ClassWriter(0); cw.visit(V1_8, ACC_PUBLIC,"json/Foo", null, "java/lang/Object", null); generateConstructor(cw); generateBar(cw); cw.visitEnd(); return cw.toByteArray(); }
  • 24.
    ASMifier Example -method p r ivate static void generateBar(ClassWriter cw) { MethodVisitor mv = cw .visitMethod(ACC_PUBLIC, "bar", "()Ljava/lang/String;", null, null); mv.visitCode(); mv.visitLdcInsn("hello"); mv.visitInsn(ARETURN); mv.visitMaxs(1, 1); mv.visitEnd(); }
  • 25.
    Converting Bytecode Intoa Class ● Need a ClassLoader! public final class ByteClassLoader extends ClassLoader { public ByteClassLoader(ClassLoader parent) { super(parent); } Class<?> loadClass(String name, byte[] bytes) { return defineClass(name, bytes, 0, bytes.length); } }
  • 26.
    Hello World, Bytecodestyle byte[] bytes = dump(); ByteClassLoader cl = new ByteClassLoader( getClass().getClassLoader()); Class<?> c = cl.loadClass(“json/Foo”, bytes); Object foo = c.newInstance(); Method m = c.getDeclaredMethod(“bar”); Object result = m.invoke(foo); System.out.println(result);
  • 27.
    Recall p ublic class BeanJsonifier implements Jsonifier<Bean> { @Override public String marshal(Bean bean) { Return "{prop1: " + bean.i + ", prop2: " + bean.s() + “}”; } }
  • 28.
    Implement an Interface cw.visit( V1_8, ACC_PUBLIC, "json/BeanJsonifier", "Ljava/lang/Object;” + “Ljson/Jsonifier<Ljson/Bean;>;", "java/lang/Object", new String[] { "json/Jsonifier" } );
  • 29.
    What we write public String marshal(Bean bean) { Return "{prop1: " + bean.i + ", prop2: " + bean.s() + “}”; }
  • 30.
    What the CompilerSees public String marshal(Bean bean) { return new StringBuilder("{prop1: ") .append(bean.i) .append(", prop2: ") .append(bean.s()) .append(“}”) .toString(); }
  • 31.
    Marshal method –Construct StringBuilder mv.visitTypeInsn( NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("{prop1: "); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false); // note – StringBuilder instance is still // on the stack, due to DUP
  • 32.
    Marshal method –Construct StringBuilder mv.visitTypeInsn( NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("prop1: "); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false); // note – StringBuilder instance is still // on the stack, due to DUP
  • 33.
    Marshal method –Construct StringBuilder mv.visitTypeInsn( NEW, ".../StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("prop1: "); mv.visitMethodInsn(INVOKESPECIAL, ".../StringBuilder", "<init>", "(L.../String;)V", false); // note – StringBuilder instance is still // on the stack, due to DUP
  • 34.
    Marshal method –Construct StringBuilder mv.visitTypeInsn(NEW, ".../StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("prop1: "); mv.visitMethodInsn(INVOKESPECIAL, ".../StringBuilder", "<init>", "(L.../String;)V", false); // note – StringBuilder instance is still // on the stack, due to DUP
  • 35.
    Marshal method –Append Field i mv.visitVarInsn(ALOAD, 1); // the passed bean mv.visitFieldInsn( GETFIELD, // what to do "json/Bean", // class holding the field "i", // field name "I"); // field type (integer) mv.visitMethodInsn(INVOKEVIRTUAL, ".../StringBuilder", "append", "(I)L.../StringBuilder;", false);
  • 36.
    Marshal method –append method getS() mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn( INVOKEVIRTUAL, // method invocation type "json/Bean", // target class "get // method name "()L.../String;", // method signature false); // not an interface mv.visitMethodInsn(INVOKEVIRTUAL, ".../StringBuilder", "append", "(L.../String;)L.../StringBuilder;", false);
  • 37.
    Marshal method –finish up mv.visitMethodInsn( INVOKEVIRTUAL, ".../StringBuilder", "toString", "()L.../String;", false); mv.visitInsn(ARETURN);
  • 38.
    Bridge Methods ●Jsonifier<T> has method String marshal(T instance) ● After erasure, this is equivalent to String marshal(Object instance) ● But we wrote String marshal(Bean instance) ● Compiler generates a bridge method
  • 39.
    Marshal Bridge Method MethodVisitor mv = cw.visitMethod( ACC_PUBLIC | ACC_BRIDGE | ACC_SYNTHETIC, "marshal", "(L.../Object;)L.../String;", null, null); // ... mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, "json/Bean"); mv.visitMethodInsn( INVOKEVIRTUAL, "json/BeanMarshaller", "marshal", "(json/Bean;)L.../String;", false); mv.visitInsn(ARETURN);
  • 40.
    Marshal Bridge Method MethodVisitor mv = cw.visitMethod( ACC_PUBLIC | ACC_BRIDGE | ACC_SYNTHETIC, "marshal", "(L.../Object;)L.../String;", null, null); // ... mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, "json/Bean"); mv.visitMethodInsn( INVOKEVIRTUAL, "json/BeanMarshaller", "marshal", "(json/Bean;)L.../String;", false); mv.visitInsn(ARETURN);
  • 41.
    Constructing an Instance ByteClassLoader loader = new ByteClassLoader( Bean.class.getClassLoader()); byte[] classBytes = dump(); Class<?> jsonifierClass = loader.loadClass( “json/BeanJsonifier”, classBytes); Jsonifier<Bean> jsonifier = (Jsonifier<Bean>) jsonifierClass.newInstance(); jsonifier.marshal(foo);
  • 42.
    Trouble in Paradise public class Bean { @Json("prop1") public int i; String s; @Json("prop2") public String getS() { return s; } }
  • 43.
    Trouble in Paradise public class Bean { @Json("prop1") private int i; String s; @Json("prop2") private String getS() { return s; } }
  • 44.
    Trouble in Paradise ● IllegalAccessError – tried to access field json.Bean.i from class json.BeanMarshaller ● Didn't happen back in the early days (as late as some versions of Java 5) ● Newer Java checks all bytecode for conformance ● Reflection can call setIsAccessible ● But reflection is slow!
  • 45.
    InvokeDynamic ● Originallyadded to support dynamic languages, but has found use far beyond that – Lambdas use it! ● First time it's invoked, effectively replaces itself with the resulting call ● Allows for excellent inlining, on par with handwritten code! ● Not available from java code – only bytecode ● ASMifier cannot help us here :(
  • 46.
    InvokeDynamic flow ●Class calls invokeDynamic, pointing to a bootstrap method... ● Which creates a MethodHandle... ● Which is wrapped in a CallSite... ● Which henceforth is run in place of the invokeDynamic call
  • 47.
    Bootstrap Method ●Takes arguments of type – MethodHandles.Lookup lookup – used for looking up MethodHandles – String name – the method name – MethodType methodType – the type of method to create – Any Bootstrap method constant arguments ● Returns a CallSite – Wrapper around a MethodHandle
  • 48.
    MethodHandles ● Typicallyconstructors, method invocations or field access ● A MethodHandle instance comes from the Lookup passed into the bootstrap method ● Lookup can see only what the calling class can see... ● But that includes reflection! ● Lookup.unreflect converts a java.lang.reflect.Method instance ● Lookup.unreflectGetter converts a j.l.r.Field into a field access call site
  • 49.
    Call Sites ●A wrapper around a Method Handle ● Can be mutable or constant ● If constant, the JVM can effectively replace the invokeDynamic opcode with the wrapped method handle – Allows for inlining and subsequent optimizations
  • 50.
    Field Access BootstrapMethod public static CallSite invokeField( MethodHandles.Lookup lookup, String name, MethodType type, Class<?> owner, String fieldName) { Field f = owner.getDeclaredField(fieldName); f.setAccessible(true); MethodHandle mh = lookup.unreflectGetter(f); return new ConstantCallSite(mh); }
  • 51.
    Method Call BootstrapMethod public static CallSite invokeMethod( MethodHandles.Lookup lookup, String name, MethodType type, Class<?> owner, String methodName) { Method m = owner.getDeclaredMethod(methodName); m.setAccessible(true); MethodHandle mh = lookup.unreflect(m); return new ConstantCallSite(mh); }
  • 52.
    Calling InvokeDynamic ●The invokeDynamic JVM instruction is called with: – Method name (passed to the bootstrap method) – Method descriptor for that which will replace the invokeDynamic call – Bootstrap method – Bootstrap method constant arguments (0 or more) ● Primitive ● String ● Class ● Method/field reference
  • 53.
    Calling invokeDynamic forField “i” mv.visitInvokeDynamicInsn( "i", // name for “method” "(Ljson/Bean;)I", // signature: int i(Bean) new Handle( // method handle H_INVOKESTATIC, // static method "json/Bootstraps", // class name "getField", // bootstrap method bootstrapSignature), // signature (TBD) "Ljson/Bean;", "i"); // bootstrap const args // mv.visitFieldInsn(GETFIELD, "json/Bean", "i", "I");
  • 54.
    Calling invokeDynamic forMethod “s” mv.visitInvokeDynamicInsn( "s", “(Ljson/Bean;)java/lang/String”, new Handle( H_INVOKESTATIC, "json/Bootstraps", "invokeMethod", bootstrapSignature), “Ljson/Bean;”, "getS"); // mv.visitMethodInsn(INVOKE_VIRTUAL, "json/Bean", "s", // "()L.../String;", false);
  • 55.
    bootstrapSignature String bootstrapSignature= MethodType.methodType( CallSite.class, Lookup.class, String.class, MethodType.class, Class.class, // first bootstrap method argument String.class)// second bootstrap method argument .toMethodDescriptorString(); // “(L.../MethodHandles$Lookup;L...;L.../MethodType;L.../Class;L.../String;)” // + “L.../CallSite;”
  • 56.
    Still to do ● Instead of building class for Bean.class, do it dynamically, based on (one-time) reflection ● Left as exercise to reader... for (Field f: clazz.getDeclaredFields()) { Json json = f.getAnnotation(Json.class); if (json != null) { generateByteCodeForAnnotatedField(f); } }
  • 57.
    Benchmarks (ns/call) Reflection HandRolled Asm 88 84 516
  • 58.
    Benchmarks (ns/call) Reflection HandRolled Asm 88 84 516 Your millage may vary...
  • 59.
    Benchmarks (ns/call) Reflection HandRolled Asm Asm w/o nulls 302 88 84 516
  • 60.
  • 61.
    CheckClassAdapter ● Canwrap ASM ClassWriter to catch some errors at bytecode generation time instead of at runtime ● Gives much more meaningful error messages ● If using COMPUTE_MAXS to ask ASM to compute size for you, then first generate bytecode, wrap CheckClassAdapter around a ClassReader
  • 62.
    Line Numbers ●cw.visitSource(“Look at JsonifierBytecodeGenerator”) ● Label label = new Label(); mv.visitLabel(label); mv.visitLineNumber(200 + fieldNumber) ● Resulting stack trace can be very useful in diagnosing bugs ● Also can declare local variables to allow step-in debugging! – mv.visitLocalVariable(...)
  • 63.
    Bootstrap Method Arguments ● Many things cannot be passed directly to a bootstrap method – Notably – non-public classes – Can pass a reference to public class with static Class variables holding the class reference ● The bootstrap method can also be part of the generated class – Probably more pain than its worth
  • 64.
    SecurityManager ● Runreflection and bytecode generation via AccessController.doPrivileged(PrivilegedAction) ● Test using java.security.Policy.setPolicy – Set policy should override implies(ProtectionDomain,Permission) ● Make sure to allow your test framework to do stuff! – Also call System.setSecurityManager(new SecurityManager());
  • 65.
    Testing ● Anygenerated bytecode needs extensive unit testing ● Test what security permissions are required ● Test different types of classes – public, private, inner, inherited, etc ● Test different types of properties – primitives, Objects, arrays, nested arrays, etc ● Test for synthetic methods ● Test stack traces for line numbers
  • 66.
    Don't Overdo it! ● Benchmark your code before optimizing at all ● Write as much code as possible directly in Java – Call out from bytecode using invokeVirtual
  • 67.
    Thank you! ●http://www.slideshare.net/nainostrebor/ ● https://github.com/irobertson/invokedynamic-talk-code ● Don't forget to fill out surveys!!!