SlideShare una empresa de Scribd logo
1 de 67
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!!!

Más contenido relacionado

La actualidad más candente

Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Tatsuhiko Miyagawa
 

La actualidad más candente (20)

How to Begin Developing Ruby Core
How to Begin Developing Ruby CoreHow to Begin Developing Ruby Core
How to Begin Developing Ruby Core
 
JRuby 9000 - Taipei Ruby User's Group 2015
JRuby 9000 - Taipei Ruby User's Group 2015JRuby 9000 - Taipei Ruby User's Group 2015
JRuby 9000 - Taipei Ruby User's Group 2015
 
Understanding Asynchronous JavaScript
Understanding Asynchronous JavaScriptUnderstanding Asynchronous JavaScript
Understanding Asynchronous JavaScript
 
Practical Testing of Ruby Core
Practical Testing of Ruby CorePractical Testing of Ruby Core
Practical Testing of Ruby Core
 
Java script for web developer
Java script for web developerJava script for web developer
Java script for web developer
 
Practical ngx_mruby
Practical ngx_mrubyPractical ngx_mruby
Practical ngx_mruby
 
JavaScript Basics and Best Practices - CC FE & UX
JavaScript Basics and Best Practices - CC FE & UXJavaScript Basics and Best Practices - CC FE & UX
JavaScript Basics and Best Practices - CC FE & UX
 
Plack - LPW 2009
Plack - LPW 2009Plack - LPW 2009
Plack - LPW 2009
 
A Re-Introduction to JavaScript
A Re-Introduction to JavaScriptA Re-Introduction to JavaScript
A Re-Introduction to JavaScript
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
 
ECMAScript 6
ECMAScript 6ECMAScript 6
ECMAScript 6
 
mruby で mackerel のプラグインを作るはなし
mruby で mackerel のプラグインを作るはなしmruby で mackerel のプラグインを作るはなし
mruby で mackerel のプラグインを作るはなし
 
Intro to Sail.js
Intro to Sail.jsIntro to Sail.js
Intro to Sail.js
 
Deep Dive async/await in Unity with UniTask(EN)
Deep Dive async/await in Unity with UniTask(EN)Deep Dive async/await in Unity with UniTask(EN)
Deep Dive async/await in Unity with UniTask(EN)
 
History & Practices for UniRx(EN)
History & Practices for UniRx(EN)History & Practices for UniRx(EN)
History & Practices for UniRx(EN)
 
Javascript
JavascriptJavascript
Javascript
 
Tatsumaki
TatsumakiTatsumaki
Tatsumaki
 
Rapid web development using tornado web and mongodb
Rapid web development using tornado web and mongodbRapid web development using tornado web and mongodb
Rapid web development using tornado web and mongodb
 
Voxxed Days Vilnius 2015 - Having fun with Javassist
Voxxed Days Vilnius 2015 - Having fun with JavassistVoxxed Days Vilnius 2015 - Having fun with Javassist
Voxxed Days Vilnius 2015 - Having fun with Javassist
 
Java Script Best Practices
Java Script Best PracticesJava Script Best Practices
Java Script Best Practices
 

Similar a Supercharging reflective libraries with InvokeDynamic

JavaScript (without DOM)
JavaScript (without DOM)JavaScript (without DOM)
JavaScript (without DOM)
Piyush Katariya
 
Invokedynamic / JSR-292
Invokedynamic / JSR-292Invokedynamic / JSR-292
Invokedynamic / JSR-292
ytoshima
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
David Padbury
 
Node js
Node jsNode js
Node js
hazzaz
 
Why Node.js
Why Node.jsWhy Node.js
Why Node.js
guileen
 

Similar a Supercharging reflective libraries with InvokeDynamic (20)

Mastering Java ByteCode
Mastering Java ByteCodeMastering Java ByteCode
Mastering Java ByteCode
 
JavaScript (without DOM)
JavaScript (without DOM)JavaScript (without DOM)
JavaScript (without DOM)
 
Marvel of Annotation Preprocessing in Java by Alexey Buzdin
Marvel of Annotation Preprocessing in Java by Alexey BuzdinMarvel of Annotation Preprocessing in Java by Alexey Buzdin
Marvel of Annotation Preprocessing in Java by Alexey Buzdin
 
Invokedynamic / JSR-292
Invokedynamic / JSR-292Invokedynamic / JSR-292
Invokedynamic / JSR-292
 
Playing With Fire - An Introduction to Node.js
Playing With Fire - An Introduction to Node.jsPlaying With Fire - An Introduction to Node.js
Playing With Fire - An Introduction to Node.js
 
Nashorn
NashornNashorn
Nashorn
 
RxSwift to Combine
RxSwift to CombineRxSwift to Combine
RxSwift to Combine
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
 
Node js
Node jsNode js
Node js
 
Writing JavaScript for C# Blazor.pptx
Writing JavaScript for C# Blazor.pptxWriting JavaScript for C# Blazor.pptx
Writing JavaScript for C# Blazor.pptx
 
JS everywhere 2011
JS everywhere 2011JS everywhere 2011
JS everywhere 2011
 
Why Nodejs Guilin Shanghai
Why Nodejs Guilin ShanghaiWhy Nodejs Guilin Shanghai
Why Nodejs Guilin Shanghai
 
Why Node.js
Why Node.jsWhy Node.js
Why Node.js
 
Advanced javascript
Advanced javascriptAdvanced javascript
Advanced javascript
 
How to Write Node.js Module
How to Write Node.js ModuleHow to Write Node.js Module
How to Write Node.js Module
 
A few good JavaScript development tools
A few good JavaScript development toolsA few good JavaScript development tools
A few good JavaScript development tools
 
RxSwift to Combine
RxSwift to CombineRxSwift to Combine
RxSwift to Combine
 
Intro to Asynchronous Javascript
Intro to Asynchronous JavascriptIntro to Asynchronous Javascript
Intro to Asynchronous Javascript
 
Let's JavaScript
Let's JavaScriptLet's JavaScript
Let's JavaScript
 
Javascript Everywhere
Javascript EverywhereJavascript Everywhere
Javascript Everywhere
 

Último

Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 

Último (20)

presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
 
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdfRising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 

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 ● 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
  • 5. (Sub)Standard Json ● Don't bother with quotes ● Allow for a trailing comma
  • 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() + “}”; } }
  • 12. Benchmarks (ns/call) Reflection HandRolled 88 516
  • 13. REFLECTION Y U SO 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 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 asm­all. 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:
  • 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 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);
  • 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 ● 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); }
  • 51. 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); }
  • 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");
  • 54. 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);
  • 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
  • 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!!!