Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Proxy 2.0

Proxy 2.0

Stop using your preferred Java proxy library now, use mine instead :)

forax

June 12, 2015
Tweet

More Decks by forax

Other Decks in Programming

Transcript

  1. Me, myself and I Assistant Professor at Paris-East University Expert

    for invokedynamic, lambda and module (JSR 292, 335 and 376) Open source developer OpenJDK, ASM, Proxy2 and JsJs Father of 3
  2. Plan What's wrong with java.lang.reflect proxies ? Proxy 2.0 –

    getting started A glimpse under the hood How to use lambda with Java 7 ?
  3. java.lang.reflect.Proxy Act as a kind of multiplexer Use j.l.r.Method to

    do the de-multiplexing String foo() int bar() void baz(int value) Object invoke(Object proxy, Method m, Object[] args) { ... m.invoke(impl, args); String foo() { ... } int baz(int value) { ... } Proxy InvocationHandler Real Implementation
  4. Java.lang.reflect.Proxy is sloooooow ! How the JDK optimize Method.invoke ?

    Object invoke(Object obj, Object... args) { // there are some security checks here !! MethodAccessor ma = this.methodAccessor ; if (ma == null) { ma = acquireMethodAccessor() ; } return ma.invoke(obj, args); }
  5. How the JDK optmize Method.invoke ? Object invoke(Object obj, Object...

    args) { ... return ma.invoke(obj, args); } package sun.reflect; interface MethodAccessor { public Object invoke(Object obj, Object[] args); }
  6. MethodAccessor Each method of the proxy provide a different MethodAccessor

    String foo() int bar() void baz(int value) Object invoke(...) { ... ma.invoke(impl, args); String foo() { ... } int bar() { … } int baz(int value) { ... } Proxy InvocationHandler Real Implementation MethodAccessors
  7. How the JIT optmize an interface call ? After profiling,

    if there are few (<= 2) impls, the JIT generate as cascade of if … else Object invoke(Object obj, Object... args) { ... if (ma.getClass() == MethodAccessor$1.class) { ((MethodAccessor$1)ma).invoke(obj, args); } else ... }
  8. Inlining ! Then the JIT can inline the code of

    the callee Object invoke(Object obj, Object... args) { ... if (ma.getClass() == MethodAccessor$1.class) { return ((Impl)obj).baz((int)args[0]); } else ... }
  9. Profile polluton Each method of the proxy provide a different

    MethodAccessor Too many MethodAccessor implementations, the VM don't inline them :( String foo() int bar() void baz(int value) Object invoke(...) { ... ma.invoke(impl, args); String foo() { ... } int bar() { … } int baz(int value) { ... } Proxy InvocationHandler Real Implementation
  10. Profile polluton vs Inlining Inlining is the mother of all

    optimizations By example, to remove boxing (escape analysis) the JIT need see the allocation and last usage in the same inlining horizon
  11. java.lang.reflect.Proxy j.l.r.Proxy API is slow – Usual JDK/VM optimizations do

    not work – 2 objects (proxy + handler) instead of one j.l.r.Proxy API is outdated – No support for default method (1.8) – Funky support of j.l.Object public methods
  12. com.github.forax.Proxy2 Separate link time and run time When first link,

    call the ProxyHandler String foo() { } int bar() { } void baz(int value) { } CallSite bootstrap( ProxyContext) { … String foo() { ... } int baz(int value) { ... } Proxy ProxyHandler Real Implementation
  13. com.github.forax.Proxy2 Use j.l.i.MethodHandle to link to the target method Subsequent

    calls use the method handle directly ! String foo() { } int bar() { } void baz(int value) { } CallSite bootstrap( ProxyContext) { … String foo() { ... } int baz(int value) { ... } Proxy ProxyHandler Real Implementation
  14. Hello Proxy public interface Hello { public String message(); }

    ProxyFactory<Hello> factory = Proxy2.createAnonymousProxyFactory(Hello.class, (ProxyContext context) -> { ... }); Hello hello = factory.create(); System.out.println(hello.message());
  15. Hello Proxy Here the proxy always return the same value

    String message() { } CallSite bootstrap( ProxyContext) { … Proxy ProxyHandler return "riviera rules"
  16. Hello Proxy ProxyFactory<Hello> factory = Proxy2.createAnonymousProxyFactory(Hello.class, (ProxyContext context) -> {

    return new ConstantCallSite( MethodHandles.dropArguments( MethodHandles.constant(String.class, "riviera rules"), 0, Object.class)); }); Hello hello = factory.create(); System.out.println(hello.message());
  17. Hello Proxy with a MethodBuilder ProxyFactory<Hello> factory = Proxy2.createAnonymousProxyFactory(Hello.class, (ProxyContext

    context) -> { return new ConstantCallSite( methodBuilder(ctx.type()) .dropAll() .callConstant("riviera rules")); }); Hello hello = factory.create(); System.out.println(hello.message());
  18. Delegate Proxy public interface Logger { public void log(String message);

    } ProxyFactory<Logger> factory = Proxy2.createAnonymousProxyFactory(Logger.class, (ProxyContext context) -> { ... }); Logger impl = System.out ::println; Logger logger = factory.create(impl); logger.log("hello riviera dev !");
  19. Delegate Proxy The proxy method is linked to the implementation

    void log(String msg) { } CallSite bootstrap( ProxyContext) { … void lambda$1(String msg) { System.out.println(msg); } Proxy ProxyHandler Implementation
  20. Delegate Proxy ProxyFactory<Logger> factory = Proxy2.createAnonymousProxyFactory(Logger.class, new Class<?>[] { Logger.class

    }, (ProxyContext ctx) -> { return new ConstantCallSite( methodBuilder(ctx.type()) .dropFirst() .unreflect(ctx.method())); }); Logger impl = System.out ::println; Logger logger = factory.create(impl); logger.log("hello riviera dev !");
  21. With an interceptor public interface Logger { public void log(String

    message); public default void enter(String msg) { System.out.println("enter !"); } } ProxyFactory<Logger> factory =... Logger impl = System.out ::println; Logger logger = factory.create(impl); logger.log("hello riviera dev !");
  22. With an interceptor enter() is called as a tailcall void

    log(String msg) { } static void enter() { ... } CallSite bootstrap( ProxyContext) { … void lambda$1(String msg) { System.out.println(msg); } Proxy ProxyHandler Implementation before
  23. With an interceptor ProxyFactory<Logger> factory = Proxy2.createAnonymousProxyFactory(Logger.class, new Class<?>[] {

    Logger.class }, (ProxyContext ctx) -> { return new ConstantCallSite( methodBuilder(ctx.type()) .dropFirst() .before(b -> b.find(Logger.class.getMethod("enter",...)) .unreflect(ctx.method())); }); ... Logger logger = factory.create(impl); ...
  24. Clean stacktrace ! public interface Logger { public void log(String

    message); public static void enter() { throw null; } } ProxyFactory<Logger> factory =... Logger impl = System.out::println; Logger logger = factory.create(impl); logger.log("hello riviera dev !"); Exception in thread "main" java.lang.NPE at Logger.enter(Logger.java:15) at Logger.main(Logger.java:33)
  25. Beter Proxy ! Look ma, there is no classloader !

    Caller and callee are fully inlined ! Stacktrace ! – Interceptors are called by tailcall – The proxy hide itself (these stacktraces are not the ones you are looking for)
  26. Under the hood How to create a proxy class ?

    How to separate link time vs runtime ? How to support non-visible interfaces ? How to hide your ass ?
  27. ASM and sun.misc.Unsafe ASM allows to generate a class at

    runtime (a class is just an array of bytecode) Unsafe.defineAnonymousClass allows to define a class with no classloader from an array of bytecode
  28. Unsafe.defineAnonymousClass Ask the VM to define an « anonymous class

    » unsafe.defineAnonymousClass(Class<?> host, byte[] bytecode, Object[] patches) patches are constant values used to replace constant pool constants
  29. invokedynamic Each method of the proxy use invokedynamic public class

    Proxy\123455 implements Logger { private final Logger arg0; public Proxy\123455(Logger arg0) { this.arg0 = arg0; } public void log(String message) { return invokedynamic(this, arg0, message); [proxyhandler-placeholder, context-placeholder] } } These constants are patched when defining the class !
  30. java.lang.invoke is special ! The proxy class is generated in

    java.lang.invoke Can use two internal annotations @Hidden (LambdaForm$Hidden) the method will not be visible in the stacktrace @ForceInline the method will always be inlined !
  31. Proxy2 is compatble with Java 7 MethodBuilder impl. is full

    of Java8 lambdas :( Need to retrofit Proxy2 lib to be Java7 compatible Easy part : rewrite class version 1.8 → 1.7 using ASM Not that easy part : support lambda on Java 7 ??
  32. Support lambda on Java 7 At creation time, a lambda

    is created using invokedynamic using j.l.i.LambdaMetaFactory bootstrap method At runtime, a lambda is an instance of a class that implements an interface thus a Proxy
  33. Eatng my own dog food ! public static CallSite metafactory(Lookup

    lookup, String name, MethodType type, MethodType sig, MethodHandle impl, MethodType reifiedSig) throws ... { Class<?>[] capturedTypes = type.parameterArray(); MethodHandle endPoint = impl.asType(reifiedSig.insertParameterTypes(0, capturedTypes)); MethodHandle mh = Proxy2.createAnonymousProxyFactory(lookup, type, new ProxyHandler() { ... public boolean override(Method method) { return Modifier.isAbstract(method.getModifiers()); } public CallSite bootstrap(ProxyContext context) { MethodHandle target = MethodHandles.dropArguments(endPoint, 0, Object.class); return new ConstantCallSite(target.asType(context.type())); } }); if (type.parameterCount() == 0) { return new ConstantCallSite(constant(type.returnType(), mh.invoke())); } return new ConstantCallSite(mh); }