This document discusses using invokeDynamic to generate bytecode at runtime in order to bypass access restrictions when using reflection. It describes how invokeDynamic works, including how bootstrap methods are used to create method handles which are then wrapped in call sites. Examples are provided of bootstrap methods for accessing fields and calling methods via reflection while avoiding the performance penalties of traditional reflection. Overall, invokeDynamic allows generating bytecode that performs equivalently to handwritten code.
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
● 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!
4. 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
6. (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, }
7. 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();
}
}
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 u blic class BeanJsonifier
implements Jsonifier<Bean> {
@Override
public String marshal(Bean bean) {
return "{prop1: " + bean.i
+ ", prop2: " + bean.getS() + “}”;
}
}
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 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
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 - ASM for Dummies
● ASM tool which reads bytecode, generates java code to
generate bytecode using ASM
● Command line:
java classpath
asmall.
jar
org.objectweb.asm.util.ASMifier
json/Foo.class
● Eclipse – Bytecode Outline Plugin (http://asm.ow2.org/eclipse/)
20. ASMifier example
Convert this:
package json;
public class Foo {
public String bar() {
return "hello";
}
}
Into this:
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 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);
}
}
26. 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);
27. Recall
p u blic 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 Compiler Sees
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);
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
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
● 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 :(
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
● 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
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 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);
}
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 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");
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);
}
}
61. 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
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
● 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());
65. 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
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!!!