invokedynamic - bardziej dynamiczna jvm (confitura 2012)

110
INVOKEDYNAMIC = bardziej dynamiczna JVM Confitura 2012 Waldek Kot [email protected] 1 wersja 1.1

Upload: waldek-kot

Post on 14-Dec-2014

258 views

Category:

Technology


0 download

DESCRIPTION

Slajdy z konferencji Confitura 2012 (http://confitura.pl), z sesji pt.: INVOKEDYNAMIC - bardziej dynamiczna JVM.

TRANSCRIPT

Page 1: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

1

INVOKEDYNAMIC = bardziej dynamiczna JVM

Confitura 2012

Waldek [email protected]

wersja 1.1

Page 2: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

2

Prezentacja mocno korzysta z animacji, więc warto się przełączyć na tryb „Slide Show”

Page 3: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

3

Prolog

Niniejsza prezentacja zawiera materiał, który chciałem zaprezentować podczas konferencji Confitura 2012 (http://confitura.pl), ale nie wszystko się udało, gdyż:• mój organizm utracił min. 50% swoich zdolności psycho-

fizycznych na skutek zbyt wysokiej temperatury, pot zalewał oczy i nawet pisanie na klawiaturze sprawiało kłopoty

• nie wszystkim było dane zobaczyć co dzieje się na ekranie (tj. rzędom od 4-go w górę na pewno nie)

… a przynajmniej tak to sobie tłumaczę

Page 4: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

4

Agenda

1. Motywacja – dlaczego taki temat ?2. Trochę teorii o InvokeDynamic3. Praktyka– zaczynając od „Hello World”

Page 5: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

5

Motywacjaczyli dlaczego zgłosiłem taki temat na Confiturę 2012 ?

Page 6: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

6

Motywacja pozytywna

InvokeDynamic (krócej: InDy) to największa nowość w Java 7

Page 7: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

7

Motywacja negatywna• bardzo mało informacji o InvokeDynamic– szczególnie w polskim Internecie• InvokeDynamic w google (PL): ~20 wyników za ostatni rok ?!?

• fatalny marketing Oracle, wpychający InvokeDynamic w niszę: „tylko dla ludzi implementujących języki dynamiczne na JVM”– to jak powiedzieć, że refleksja w Javie jest użyteczna tylko dla

wybrańców– pewnie konsekwencją tego jest mała liczba i słabe tutoriale (na

pewno nie są to tutoriale typu „Hello World”), np.: http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html

• wrodzona przekora i syndrom „bo ja to widzę inaczej”

Page 8: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

8

Dlaczego warto poznać InDy ?• nowy, fundamentalny składnik JVM

– nowy format klas (constant pool, .class)– nowy bytecode (po raz pierwszy w historii technologii Java !)– nowe API w podstawowym pakiecie java.lang

• Java 8: łatwiej będzie zrozumieć implementację lambda expressions (czyli upraszczając: „closures w Java”)– oba projekty, tj. invokedynamic i lambda expressions mają ze sobą wiele

wspólnego• http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html• http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html

• wpływ na inne-niż-Java języki programowania dla JVM– OK, trudno, ale muszę wspomnieć o wykorzystaniu invokedynamic przez

języki dynamiczne na JVM (np. Groovie, Scala, JRuby, Jython, JavaScript)• ale moja prezentacja w ogóle nie porusza tematu języków dynamicznych dla JVM ! Będę

mówić wyłącznie o wykorzystaniu InDy w języku Java.

Page 9: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

9

Po co InvokeDynamic ?

Żeby łatwiej tworzyć dynamiczny, generyczny kod, w dodatku wydajnie wykonywany przez JVM.• z tego samego powodu mamy w Java m.in. refleksję,

adnotacje, dynamiczne ładowanie kodu czy generyki

Page 10: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

10

Po co InvokeDynamic ?

• … ale: wszystko co daje InDy można dzisiaj zrobić innymi metodami („zasymulować”)

• prawda, tyle tylko, że dzięki InDy:– będzie prościej, bo… nie musimy tego robić• „Najlepszy kod to taki którego nie trzeba pisać”

– będzie wydajniej, bo:• kod z InDy jest lepiej „rozumiany” przez kompilator JIT JVM=> JIT będzie w stanie wykonać o wiele więcej optymalizacji, niż przy zastosowaniu „symulatorów”

Page 11: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

11

Ważna uwaga• większość przykładów i kodu, który tutaj pokazuję ukradłem

– adaptacja filozofii: „najlepszy kod, to taki, którego nie trzeba pisa攕 mój mały wkład tutaj polega na tym, aby:

1. „odczarować” temat InvokeDynamic2. poprzez przykłady - czasem trywialne - zachęcić do samodzielnego

poznawania InDy3. ułatwić czytanie ze zrozumieniem kodu wykorzystującego InDy,

dostępnego w sieci• i czerpać z tego taką przyjemność, jaką ja miałem (i mam nadal)• choćby kodu publikowanego tutaj:

– http://code.google.com/p/jsr292-cookbook/– http://www.java.net/blogs/forax/ – https://blogs.oracle.com/jrose/– http://blog.headius.com/

Page 12: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

12

Krótka teoria InvokeDynamic

Page 13: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

13

InvokeDynamic

Pozwala programiście mieć kontrolę nad tak czymś tak elementarnym, jak wywoływanie

metod

Page 14: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

14

MethodHandle

wskaźnik / uchwyt / referencja do metody• „metoda” oznacza tutaj także operację dostępu

do pól (obiektów i klas), konstruktorów, super, itd.– a nawet dostęp do… metod, które nie istnieją .

Page 15: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

15

MethodHandle - Hello World

• do zabawy z większością tego co oferuje Indy wystarczy Java 7 i IDE

• czyli tyle wystarczy rozumieć, aby zacząć :

public class HelloIndyWorld {public static void main(String[] args) {}

}

Page 16: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

16

MethodHandle – Hello Worldpackage pl.confitura.invokedynamic;

import java.lang.invoke.MethodHandle;import java.lang.invoke.MethodHandles;

public class HelloIndyWorld {public static void main(String[] args) throws Throwable {

MethodHandle mh = MethodHandles.constant(String.class, "Hello World !");System.out.println(mh.invoke());

}}

Do tworzenia uchwytów do metod używamy m.in. metod (statycznych) klasy MethodHandles.

Śmiesznie, ale w tym przykładzie tworzymy uchwyt do nieistniejącej metody !constant zawsze zwraca stałą wartość, tu: typu String, równą „Hello World !”.Metoda invoke wywołuje metodę na którą wskazuje uchwyt.

Page 17: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

17

Więcej przykładów później

Page 18: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

18

MethodHandle• jest bardzo lekką konstrukcją

– (sorry za powtarzanie się) zrozumiałą dla JIT (a także GC) w JVM• niemal natychmiastowe wykorzystanie MethodHandle to zastąpienie

nimi refleksji– refleksja (java.lang.reflect), mimo iż od Java 1.4 poważnie

udoskonalona, to jest znacznie wolniejsza od MethodHandle. Powody są dwa:1. przy każdym wywołaniu metody z użyciem refleksji [poprzez

invoke() z java.lang.reflect.Method], następuje sprawdzenie praw dostępu kodu wołającego do tej metody. W MethodHandle, to sprawdzenie następuje tylko przy pobieraniu uchwytu do metody [np. za pomocą lookup()].

2. w refleksji konieczny jest boxing i inne konwersje. Z kolei, uchwyt do metody jest silnie-typizowany, w tym możliwe jest posługiwanie się prymitywami bez ich konwersji do typów referencyjnych.

Page 19: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

19

MethodHandle - kombinatory• API udostępnione w ramach InvokeDynamic pozwala na

całkiem złożone „manipulacje” uchwytami do metod– możemy dostawać się do różnego rodzaju metod (statycznych,

wirtualnych, konstruktorów, itp.), a także w szerokim zakresie manipulować ich typami, parametrami, zwracanymi wynikami, itp.

– jest nawet konstrukcja przypominająca if-then-else– zasadniczo to API MethodHandle jest „Turing complete”

• zwykle wynikiem tych manipulacji są nowe uchwyty do metod• możliwe jest „składanie” manipulacji (jak funkcji: f ( g ( h (x) ) )• ciąg (łańcuch) manipulacji na uchwytach tworzy graf• ten graf (=intencja programisty) jest zrozumiała dla

kompilatora JIT maszyny wirtualnej Java– To jest kluczowe źródło wydajności InvokeDynamic, bo mimo potencjalnie dużej

złożoności całego grafu manipulacji), JIT wciąż (w dużym stopniu) jest w stanie aplikować swoje optymalizacje np. method inlining

– JIT może taki graf „przejść”, węzeł po węźle i „zrozumieć” go

Page 20: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

20

MethodType – typ metody• związany z MethodHandle• określa dokładny typ metod i uchwytów do metod

– czyli: typy parametrów metody i zwracanego przez nią wyniku• mimo dodania InvokeDynamic, JVM nie przestaje być silnie typizowalna

(strong typing)– a zatem te z optymalizacji JIT, które korzystają z informacji o typach, mogą być

wciąż aplikowane• methodType posiada metody pozwalające na tworzenie odpowiednich

typów oraz manipulacje na nich• wskazówka: mimo, iż to mało atrakcyjne zagadnienie, to warto je dobrze

poznać, bo wiele błędów w zabawach z InDy bierze się z niezgodności typów, a:

1. w zdecydowanej większości dają one o sobie znać dopiero w czasie wykonania (kompilator daje tu niewiele)

2. JVM jest absolutnie bezwzględny jeśli chodzi o zgodność typów !• bezpieczeństwo i szybkość

Page 21: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

21

Nowy bytecode: INVOKEDYNAMIC

Pierwsze w historii rozszerzenie listy instrukcji JVM !• choć można spekulować, że o czymś takim myślano już w

momencie powstawania technologii Java, bo:– kod tej instrukcji był od początku zarezerwowany (BA)– nieprzypadkowo (?) jest ulokowany razem z pozostałymi

instrukcjami wywołania metod (INVOKESTATIC, INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE)

– tak naprawdę, to historycznie HotSpot VM powstał nie dla języka Java, a dla języków… Smalltalk oraz Scheme , w których jest znacznie większa niż w języku Java możliwość wpływu programisty na sposób wywoływania metod

Page 22: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

22

Bytecode: INVOKEDYNAMIC• przy pierwszym napotkaniu tej instrukcji, JVM wywołuje metodę (tzw.

BSM – bootstrap method), której zadaniem jest określić docelową metodę (uchwyt) która będzie wywołana

• To programista tworzy bootstrap method. W sumie to jest zwykła metoda…

• BSM (tj. jej nazwa, klasa w której ona się znajduje, jej typ - parametry, wynik - oraz opcjonalnie dodatkowe parametry) są obowiązkowymi argumentami instrukcji INVOKEDYNAMIC

• BSM JEST ZAWSZE WYKONYWANA TYLKO 1 RAZ !– i TYLKO przy PIERWSZYM napotkaniu danego wywołania InvokeDynamic !

• w argumentach INVOKEDYNAMIC, po tych które dotyczą BSM, następują pozostałe argumenty (np. nazwa wywoływanej metody i jej argumenty)

Page 23: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

23

CallSite

• wynikiem wykonania się BSM jest utworzenie obiektu klasy java.lang.invoke.CallSite

• wewnątrz tego obiektu jest docelowa metoda (uchwyt), którą wykona instrukcja INVOKEDYNAMIC, po powrocie z BSM

• „CallSite” czyli: „miejsce wywołania metody”– czyli de facto dostajemy referencję do „miejsca” w którym

umieszczona jest instrukcja INVOKEDYNAMIC• czyli potencjalnie możemy zmodyfikować to „miejsce”, tj.

podstawić tam inny uchwyt– dynamizm InDy w akcji !

Page 24: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

24

CallSite

• API udostępnia 3 specjalizowane klasy CallSite:– ConstantCallSite – czyli informujemy JVM, że po wykonaniu

BSM, docelowa metoda (target), nie ulegnie zmianie• czyli informujemy JIT, że może bez obawy aplikować te ze swoich

optymalizacji, które zakładają, że zawsze będzie wywoływana ta sama metoda (czyli należy oczekiwać, że szybkość takiego wywołania będzie porównywalna do wywołania „zwykłego”, np. statycznego)

– MutableCallSite (oraz jej wariant VolatileCallSite), które informują JIT, że docelowa metoda (target) może ulec zmianie

• można tworzyć swoje własne specjalizacje klas CallSite ! – z własną logiką, stanem, itp.

Page 25: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

25

Inne przydatne konstrukcjeAPI InvokeDynamic udostępnia jeszcze kilka innych przydatnych konstrukcji pomocniczych (prostszych i/lub bardziej wydajnych niż gdyby samodzielnie budować ich odpowiedniki):• java.lang.ClassValue<T> – pozwala programiście przypisać do obiektów Class

swoje własne wartości. Najczęściej używane jako bufor, podobny do ThreadLocal, z tym, że zamiast z wątkiem, wartości są „związane” z klasą – będzie później przykład pokazujący do czego i jak taki bufor można użyć

• SwitchPoint – rodzaj semafora, który bezpiecznie wątkowo może poinformować o pewnej zmianie związane z nim uchwyty– będzie później przykład praktyczny, który to lepiej objaśni

• MethodHandleProxies – pozwala utworzyć obiekt implementujący określony interfejs z 1 metodą (czyli tzw. SAM – Single Abstract Method); „implementacją” ten metody będzie podany uchwyt– też będzie przykład (i to krótki, acz super-fajny)

Page 26: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

26

Czas na DEMO !(wiem, nareszcie )

Page 27: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

27

DEMO Iczyli zabawy z MethodHandle

Page 28: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

28

Co potrzeba ?

• Java 7– im wyższa wersja, tym lepszej wydajności

InvokeDynamic należy się spodziewać• IDE– tworzymy zwykły projekt Java

Page 29: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

29

MethodHandle – Hello World (było)

package pl.confitura.invokedynamic;

import java.lang.invoke.MethodHandle;import java.lang.invoke.MethodHandles;

public class HelloIndyWorld {public static void main(String[] args) throws Throwable {

MethodHandle mh = MethodHandles.constant(String.class, "Hello World !");System.out.println(mh.invoke());

}}

Do tworzenia uchwytów do metod używamy m.in. metod (statycznych) klasy MethodHandles.

Śmiesznie, ale w tym przykładzie tworzymy uchwyt do nieistniejącej metody !Constant zawsze zwraca stałą wartość, tu: typu String, równą „Hello World !”.Metoda invoke wywołuje metodę na którą wskazuje uchwyt.

Page 30: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

30

MethodHandle – identity

public class HelloIndyWorld {public static void main(String[] args) throws Throwable {

MethodHandle mh = MethodHandles.identity(String.class);System.out.println(mh.invoke("Hello InDy World !"));

}}

Uchwyt do metody uzyskany poprzez identity, gdy jest wywołany, zwraca swój argument (podanego typu, tu: String)

To jest argument do uchwytu do metody (tu: mh). Te argumenty będą przekazane do metody na którą wskazuje uchwyt (tu: identity).

Page 31: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

31

MethodHandle – type

public class HelloIndyWorld {public static void main(String[] args) throws Throwable {

MethodHandle mh = MethodHandles.identity(String.class);System.out.println(mh.type());

}} Pozwala określić typ metody, czyli jakiego

typu są parametry i wynik podanej metody.Więcej informacji: klasa MethodType w java.lang.invoke

Page 32: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

32

MethodHandle – invoke, invokeExact

public class HelloIndyWorld {public static void main(String[] args) throws Throwable {

MethodHandle mh = MethodHandles.identity(String.class);//System.out.println(mh.invoke("Hello InDy World !"));System.out.println(mh.invokeExact("Hello InDy World !"));

}}

Dostępnych jest kilka sposobów wywołania uchwytu do metody: • invoke• invokeExact• invokeWithArgumentsInvoke dokonuje opcjonalnie konwersji argumentów i wyniku do tego co oczekuje wywołujący (caller). InvokeExact jest szybszy, ale wymagana jest 100% zgodność typów między wywołującym a metodą (uchwytem). Tu: println oczekuje Object, a invokeExact zwraca String. Konwersja NIE jest wykonywana. Stąd błąd czasu wykonania (poniżej). Bo konieczny jest cast do String.

Page 33: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

33

MethodHandle – Polymorphic Signature

• InvokeDynamic wprowadza także ciekawostkę w postaci tzw. polimorficznych sygnatur metod

• zauważ, że sygnatura metod invoke/invokeExact wynika z tego ile i jakie parametry do niej są przekazywane !

• kompilator Java i weryfikator bytecode’u (a także narzędzia, tu: Eclipse) dopuszczają jako poprawne takie wywołania. Niestety, adnotacja @PolymorphicSignature nie jest dostępna dla naszego kodu …

Page 34: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

34

MethodHandle – coś ciekawszego: uchwyt do własnej metody

public class HelloIndyWorld {public static class MyClass {

static public String myConcat(String s1, String s2) {return s1 + s2;

}}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(String.class, String.class, String.class);MethodHandle mh = MethodHandles.lookup().findStatic(MyClass.class, "myConcat", mt);System.out.println((String) mh.invokeExact("Ala ", "ma kota"));

}}

Lookup pozwala uzyskać uchwyt do różnorodnych metod, np. tutaj do metody statycznej myConcat w klasie MyClass.

W 100% muszą zgadzać się nie tylko nazwa metody, ale także jej sygnatura (określona przez typ metody – MethodType [mt])

Page 35: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

35

MethodHandle – uchwyt do metody wirtualnejpublic class HelloIndyWorld {

public static class MyClass {private String s;

public MyClass(String s) {this.s = s;

}

public int howManyCharacters(String s) {return (s + this.s).length();

}}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(int.class, String.class);MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "howManyCharacters", mt);MyClass obj = new MyClass("Ala");MethodHandle mhBound = mh.bindTo(obj);System.out.println((int) mhBound.invokeExact("ma kota"));

}}

Metody wirtualne klasy wyszukujemy za pomocą findVirtual.

W metodach wirtualnych, implicite, ich pierwszy argument określa obiekt na którym ta metoda będzie wykonana (receiver). Za pomocą bindTo możemy utworzyć uchwyt do metody, w którym ten argument (receiver) będzie na stałe ustawiony na wybrany obiekt.

Page 36: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

36

MethodHandle – uchwyt do metody wirtualnej (2)public class HelloIndyWorld {

public static class MyClass {private String s;

public MyClass(String s) {this.s = s;

}

public int howManyCharacters(String s) {return (s + this.s).length();

}}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(int.class, String.class);MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "howManyCharacters", mt);System.out.println((int) mh.invokeExact(new MyClass("Ala"), "ma kota"));

}} Można też tak…

Page 37: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

37

MethodHandle – składanie uchwytów (kombinatory)public class HelloIndyWorld {

public static class MyClass {private String s;

public MyClass(String s) {this.s = s;

}

public String myVirtualConcat(String s) {return this.s + s;

}}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(String.class, String.class);MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);mh = mh.bindTo(new MyClass("Ala "));

MethodType mtToUpper = MethodType.methodType(String.class);MethodHandle mhToUpper = MethodHandles.lookup().findVirtual(String.class, "toUpperCase", mtToUpper);

MethodHandle mhCombined = MethodHandles.filterArguments(mh, 0, mhToUpper); System.out.println((String) mhCombined.invokeExact("ma kota"));

}}

Przykład utworzenia nowego uchwytu do metody – za pomocą metody filterArgument. Najpierw na podanym argumencie (0, czyli „ma kota”), wykona się metoda wskazywana przez uchwyt mhToUpper (czyli metoda wirtualna „toUpperCase” dla obiektu klasy String). Zwrócony z niej wynik (czyli „MA KOTA”) będzie nowym argumentem dla mh (pierwszy argument w filterArguments).

Page 38: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

38

MethodHandle – filtrowanie wyników metodpublic class HelloIndyWorld {

public static class MyClass {private String s;

public MyClass(String s) {this.s = s;

}

public String myVirtualConcat(String s) {return this.s + s;

}}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(String.class, String.class);MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);mh = mh.bindTo(new MyClass("Ala "));

MethodType mtToUpper = MethodType.methodType(String.class);MethodHandle mhToUpper = MethodHandles.lookup().findVirtual(String.class, "toUpperCase", mtToUpper);

MethodHandle mhCombined = MethodHandles.filterReturnValue(mh, mhToUpper); System.out.println((String) mhCombined.invokeExact("ma kota"));

}}

Tu najpierw wykonywana jest metoda na którą wskazuje mh (czyli myVirtualConcat), a następnie na jej wyniku (czyli „Ala ma kota”) wykonywana jest metoda na którą wskazuje mhToUpper.

Page 39: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

39

MethodHandle – interceptor typu ‚before’public class HelloIndyWorld {

public static class MyClass {private String s;public MyClass(String s) {

this.s = s;}

public String myVirtualConcat(String s) {return this.s + s;

}

public static String myInterceptor(String s) {System.out.println("Intercepted, with arg s: " + s);return "^" + s + "^";

}}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(String.class, String.class);MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);mh = mh.bindTo(new MyClass("Ala "));

MethodType mtInterceptor = MethodType.methodType(String.class, String.class);MethodHandle mhInterceptor = MethodHandles.lookup().findStatic(MyClass.class, "myInterceptor", mtInterceptor);

MethodHandle mhCombined = MethodHandles.foldArguments(mh, mhInterceptor);System.out.println((String) mhCombined.invokeExact("ma kota"));

}}

foldArguments działa w ten sposób, że najpierw wykonywany jest drugi argument (mhInterceptor), a jego wynik (o ile nie jest void) jest WSTAWIANY (INSERT) jako argument do wywołania pierwszego argumentu (mh).Po czym wywoływany jest mh.Czyli poniższy kod nie zadziała. Dlaczego ? Bo po wstawieniu dodatkowego argumentu (czyli wyniku z mhInterceptor) nie zgadzają się typy uchwytów w foldArguments (a muszą one być takie same). Komunikat błędu jest przy tym dosyć mylący …

Page 40: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

40

MethodHandle – interceptor typu ‚before’ (poprawniej)public class HelloIndyWorld {

public static class MyClass {private String s;public MyClass(String s) {

this.s = s;}

public String myVirtualConcat(String s) {return this.s + s;

}

public static String myInterceptor(String s) {System.out.println("Intercepted, with arg s: " + s);return "^" + s + "^";

}}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(String.class, String.class);MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);mh = mh.bindTo(new MyClass("Ala "));

MethodType mtInterceptor = MethodType.methodType(String.class, String.class);MethodHandle mhInterceptor = MethodHandles.lookup().findStatic(MyClass.class, "myInterceptor", mtInterceptor);

MethodHandle mhCombined = MethodHandles.foldArguments(MethodHandles.dropArguments(mh, 1, String.class), mhInterceptor);

System.out.println((String) mhCombined.invokeExact("ma kota"));}

}

Za pomocą metody dropArguments trzeba pozbyć się niepotrzebnego już argumentu (czyli „ma kota”). Pozycja argumentów liczona jest od 0, ale ponieważ wynik wywołania mhInterceptor jest WSTAWIANY na pozycję 0, to oryginalne argumenty przesuwają się (czyli „ma kota” jest teraz na pozycji 1).

Page 41: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

41

MethodHandle – interceptor typu ‚before’ (najpoprawniej )public class HelloIndyWorld {

public static class MyClass {private String s;public MyClass(String s) {

this.s = s;}

public String myVirtualConcat(String s) {return this.s + s;

}

public static String myInterceptor(String s) {System.out.println("Intercepted, with arg s: " + s);return "^" + s + "^";

}}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(String.class, String.class);MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);mh = mh.bindTo(new MyClass("Ala "));

MethodType mtInterceptor = MethodType.methodType(String.class, String.class);MethodHandle mhInterceptor = MethodHandles.lookup().findStatic(MyClass.class, "myInterceptor", mtInterceptor);

MethodHandle mhCombined = MethodHandles.foldArguments(MethodHandles.dropArguments(mh, 1, mh.type().parameterType(0)), mhInterceptor);

System.out.println((String) mhCombined.invokeExact("ma kota"));}

}

Bo tak jest bardziej generycznie. Typ usuwanego argumentu jest zgodny z typem parametru metody myVirtualConcat). Dla wyjaśnienia:• typ mh to: (String)String• mh.type().parameterType(0) odnosi się do

pierwszego (i tu jedynego) argumentu metody myVirtualConcat, sprzed wstawienia wyniku z interceptora

Page 42: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

42

MethodHandle – interceptor typu ‚after’public class HelloIndyWorld {

public static class MyClass {private String s;public MyClass(String s) {

this.s = s;}

public String myVirtualConcat(String s) {return this.s + s;

}

public static String myInterceptor(String s) {System.out.println("Intercepted, with arg s: " + s);return "^" + s + "^";

}}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(String.class, String.class);MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);mh = mh.bindTo(new MyClass("Ala "));

MethodType mtInterceptor = MethodType.methodType(String.class, String.class);MethodHandle mhInterceptor = MethodHandles.lookup().findStatic(MyClass.class, "myInterceptor", mtInterceptor);

MethodHandle mhCombined = MethodHandles.foldArguments(MethodHandles.dropArguments(mhInterceptor, 1, mh.type().parameterType(0)), mh);

System.out.println((String) mhCombined.invokeExact("ma kota"));}

}

Page 43: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

43

MethodHandles / MethodHandleDostępne są także metody:• wstawiające argumenty wybranego typu (insertArgument)• zmieniające kolejność argumentów (permuteArguments)• tworzące uchwyty przechwytujące wyjątki (catchException)• tworzące uchwyty rzucające wyjątki (throwException)• konwertujące argumenty do podanych typów (MethodHandle.asType)• dopasowujące uchwyt, tak aby argumenty były przekazywane w

postaci tablicy (MethodHandle.asCollector) lub odwrotnie (MethodHandle.asSpreader)

• obsługujące uchwyty o zmiennej liczbie parametrów (MethodHandle.asVarargsCollector)

• …

Page 44: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

44

MethodHandles

• jest nawet dostępna konstrukcja IF-THEN-ELSE !– MethodHandles.guardWithTest

• szczegóły (m.in. dotyczące typów argumentów) są podane w dokumentacji API:– http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandles.html

Page 45: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

45

MethodHandle – przykład z .guardWithTestpublic class HelloIndyWorld {

public static class MyClass {public static String withDog(String s) {

return s + " ma psa";}

public static String withCat(String s) {return s + " ma kota";

}}

public static void main(String[] args) throws Throwable {MethodType mtDog = MethodType.methodType(String.class, String.class);MethodHandle mhDog = MethodHandles.lookup().findStatic(MyClass.class, "withDog", mtDog);

MethodType mtCat = MethodType.methodType(String.class, String.class);MethodHandle mhCat = MethodHandles.lookup().findStatic(MyClass.class, "withCat", mtCat);

MethodHandle mhTest = MethodHandles.identity(boolean.class);mhTest = MethodHandles.dropArguments(mhTest, 1, mhDog.type().parameterType(0));

mhDog = MethodHandles.dropArguments(mhDog, 0, boolean.class);mhCat = MethodHandles.dropArguments(mhCat, 0, boolean.class);

MethodHandle mhCombined = MethodHandles.guardWithTest(mhTest, mhDog, mhCat);

System.out.println((String) mhCombined.invokeExact(true, "Ala"));System.out.println((String) mhCombined.invokeExact(false, "Ala"));

}}

Page 46: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

46

MethodHandle i refleksja (java.lang.reflect)

public class HelloIndyWorld {

public static void main(String[] args) throws Throwable {java.lang.reflect.Method m = String.class.getDeclaredMethod("toUpperCase");

MethodHandle mh = MethodHandles.lookup().unreflect(m);

System.out.println((String) mh.invokeExact("Ala ma kota"));}

}

Page 47: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

47

MethodHandleProxies

• możliwe jest tworzenie obiektów, implementujących podany interfejs (typu SAM, czyli z jedną metodą, tzw. interfejs funkcyjny)

• implementacją tej metody będzie metoda wskazywana przez podany uchwyt

Page 48: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

48

MethodHandleProxies - przykładpublic class HelloIndyWorld {

public interface MyInterface {String myMethod(String s1, String s2);

}

public static class MyClass {public static String myConcat(String s1, String s2) {

return s1 + s2;}

}

public static void main(String[] args) throws Throwable {MethodType mt = MethodType.methodType(String.class, String.class, String.class);MethodHandle mh = MethodHandles.lookup().findStatic(MyClass.class, "myConcat", mt);

MyInterface myObj = MethodHandleProxies.asInterfaceInstance(MyInterface.class, mh);

System.out.println( myObj.myMethod("Ala ", "ma kota") );}

}

Page 49: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

49

DEMO IIczyli wywołanie instrukcji bytecode’u INVOKEDYNAMIC

Page 50: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

50

Mały problem• niestety, ale w Java 7, w języku Java nie ma składni pozwalającej na

bezpośrednie tworzenie dynamicznych wywołań metod• na początku powstawania specyfikacji InvokeDynamic taka składnia

była dostępna:String result2 = java.dyn.Dynamic.<String>getName(file);

• podjęto (IMHO słuszną) decyzję, aby składniowo zarówno InvokeDynamic, jak i lambda expressions były do siebie jak najbardziej zbliżone– niestety: lambda expressions wypadła z Java 7 i ma się pojawić w Java 8

• niestety: wydanie Java 8 przesunęło się z początkowego „late-2012” na „late-2013” (albo i jeszcze później). Niestety .

• trzeba poradzić sobie z problemem poprzez samodzielne generowanie bytecode’u (zawierającego instrukcję INVOKEDYNAMIC)– brzmi hardcore’owo, ale narzędzia takie jak ASM (

http://asm.ow2.org) znacznie to ułatwiają

Page 51: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

51

Generowanie instrukcji INVOKEDYNAMIC

• W sieci jest kilka przykładów jak generować instrukcję INVOKEDYNAMIC– http://weblogs.java.net/blog/forax/archive/2011/01/07/calling-invokedynamic-java– http://nerds-central.blogspot.com/2011/05/performing-dynamicinvoke-from-java-step.html

• W moich przykładach (kod „edukacyjny”) zależało mi, aby było to jak najprostsze (czyli np. bez potrzeby patchowania .class, jak w większości przykładów z JSR-292 Cookbook)– http://code.google.com/p/jsr292-cookbook

Page 52: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

52

Wymagania

• Tak jak poprzednio, czyli:– Java 7– IDE

• Dodatkowo:– ASM (http://forge.ow2.org/projects/asm)• asm-all-4.0.jar• najnowsze wersje są OK (tu używam ASM 4.0)

Page 53: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

53

Github

• Tutaj znajduje się kod źródłowy pokazywanych przykładów:– https://github.com/waldekkot/Confitura2012_InvokeDynamic

• Sposób wywoływania instrukcji INVOKEDYNAMIC znajduje się w w/w repozytorium w projekcie DemoIndySecond:

https://github.com/waldekkot/Confitura2012_InvokeDynamic/tree/master/DemoIndySecond

• Ale na kolejnych slajdach będę poszczególne kroki wyjaśniać…

Page 54: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

54

Przykład (DemoIndySecond)

public class HelloInDyWorld1 {public static String myConfituraMethod(String s) { return s + " 2012"; }

public static CallSite myBSM(MethodHandles.Lookup caller, String methodName, MethodType methodType, Object... bsmArgs) {MethodHandle mh = null;try {

mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod",MethodType.methodType(String.class, String.class));

} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }

return new ConstantCallSite(mh);}

public static void main(String args[]) throws Throwable {MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class),

"myBSM", HelloInDyWorld1.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class));

System.out.println( mh.invoke("Confitura") );}

}

Page 55: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

55

DemoIndySecond (krok I)

public class HelloInDyWorld1 {public static String myConfituraMethod(String s) { return s + " 2012"; }

public static CallSite myBSM(MethodHandles.Lookup caller, String methodName, MethodType methodType, Object... bsmArgs) {MethodHandle mh = null;try {

mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod",MethodType.methodType(String.class, String.class));

} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }

return new ConstantCallSite(mh);}

public static void main(String args[]) throws Throwable {MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class),

"myBSM", HelloInDyWorld1.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class));

System.out.println( mh.invoke("Confitura") );}

}

InvokeDynamic.prepare, przygotowuje („wstawia” do kodu Java) instrukcję bytecode’u INVOKEDYNAMIC, dzięki której możliwe jest dynamiczne wywoływanie metod i kontrola nad tym procesem.

Implementacja InvokeDynamic.prepare jest wyjaśniona na późniejszych slajdach.

„run me” to odpowiednik nazwy wywoływanej metody (czyli tak jakby wywołać X.runme). A przynajmniej tak jak my, jako wywołujący (caller), to „widzimy”. Warto zauważyć, że nie obowiązują nas ograniczenia co do identyfikatorów (tu jest spacja w nazwie wywoływanej metody ).

Drugi argument, to typ wywoływanej metody (znowu: z punktu widzenia wywołującego).

Kolejne argumenty dotyczą BSM (bootstrap method), tj. jej nazwy, klasy w której ona się znajduje, jej typu oraz jej (opcjonalnych) parametrów. Metoda BSM:• jest zawsze wykonywana co najwyżej jeden raz• i tylko przy pierwszym napotkaniu przez JVM danej instrukcji

INVOKEDYNAMIC (czyli very lazy, fakt, który jeszcze wykorzystamy…)

Page 56: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

56

DemoIndySecond(krok II)

public class HelloInDyWorld1 {public static String myConfituraMethod(String s) { return s + " 2012"; }

public static CallSite myBSM(MethodHandles.Lookup caller,String methodName,MethodType methodType, Object...bsmArgs) {

MethodHandle mh = null;try {

mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod",MethodType.methodType(String.class, String.class));

} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }

return new ConstantCallSite(mh);}

public static void main(String args[]) throws Throwable {MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class),

"myBSM", HelloInDyWorld1.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class));

System.out.println( mh.invoke("Confitura") );}

}

Bootstrap method.

Jej zadaniem jest skonstruować dane miejsce wywoływania metody, czyli określić za pomocą uchwytu do metody (MethodHandle) docelową metodę (target), która będzie wywoływana kiedy JVM napotka daną instrukcję INVOKEDYNAMIC.Powtarzam się, ale: BSM jest wywoływana co najwyżej raz, przy pierwszym napotkaniu tę instrukcji INVOKEDYNAMIC. Po wykonaniu się BSM (tj. ustaleniu docelowej metody), obecne i każde następne wykonanie tej instrukcji INVOKEDYNAMIC spowoduje wykonanie ustalonej przez BSM docelowej metody. BSM zwraca obiekt opakowujący uchwyt do docelowej metody, czyli CallSite. Możemy też przekazać naszą intencję, czy chcemy w przyszłości zmieniać docelową metodę (w tym przykładzie zwracamy ConstantCallSite, czyli informujemy JIT, że to miejsce wywołania będzie zawsze już odnosiło się do metody ustalonej w BSM). Referencję do obiektu CallSite można przechowywać w swoim kodzie. Można także tworzyć własne klasy specjalizowane CallSite.Typ uchwytu do metody zwracanego przez BSM musi być w 100% zgodny z tym, co określił wywołujący (caller) – tu: w drugim argumencie prepare.Do BSM, z miejsca wywołania metody, caller może przekazać dodatkowe parametry (bsmArgs). W sumie to także nazwa wywoływanej metody („run me”) jest takim dodatkowym parametrem, bo jedyne co musi być zachowane, to typ uchwytu zwracany z BSM - musi być zgodny z tym, co określił caller w callsite.

Page 57: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

57

DemoIndySecond(krok III)

public class HelloInDyWorld1 {public static String myConfituraMethod(String s) { return s + " 2012"; }

public static CallSite myBSM(MethodHandles.Lookup caller, String methodName, MethodType methodType, Object... bsmArgs) {MethodHandle mh = null;try {

mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod",MethodType.methodType(String.class, String.class));

} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }

return new ConstantCallSite(mh);}

public static void main(String args[]) throws Throwable {MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class),

"myBSM", HelloInDyWorld1.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class));

System.out.println( mh.invoke("Confitura") );}

}

Docelowa metoda (target).

Jej sygnatura (=typ, MethodType) NIE musi być w 100% zgodna z tym, co określił caller w miejscu wywołania (callsite). Za pomocą ciągu kombinatorów na uchwycie do tej metody, możliwe jest odpowiednie dopasowanie tej metody do wymagań (typu) określonego w miejscu wywoływania metody.

To pewnie oczywiste, ale to co przychodzi jako argument tej metody (tu: String s), to są argumenty przekazane w wywołaniu metody (tu: „Confitura”). No chyba że, ciąg kombinatorów to zmodyfikował (, będzie później przykład pięknie to pokazujący).

Page 58: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

58

DemoIndySecond(krok IV)

public class HelloInDyWorld1 {public static String myConfituraMethod(String s) { return s + " 2012"; }

public static CallSite myBSM(MethodHandles.Lookup caller, String methodName, MethodType methodType, Object... bsmArgs) {MethodHandle mh = null;try {

mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod",MethodType.methodType(String.class, String.class));

} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }

return new ConstantCallSite(mh);}

public static void main(String args[]) throws Throwable {MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class),

"myBSM", HelloInDyWorld1.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class));

System.out.println( mh.invoke("Confitura") );}

}

Dynamiczne wywołanie metody.

Page 59: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

59

InvokeDynamic.prepare• własny kod pomocniczy (z braku składni w Java dla InvokeDynamic)• pozwala utworzyć dynamiczne wywołanie metody z poziomu

zwykłego kodu Java– korzysta z generatora bytecode’u (ASM)– generuje nową klasę, a w niej metodę zawierającą instrukcję

INVOKEDYNAMIC - dynamicznego wywoływania metody– ta instrukcja jest „skonfigurowana”

• poprzez parametry przekazane do InvokeDynamic.prepare– nazwa i typ wywoływanej metody (=callsite)– metoda bootstrap (BSM)

• w InvokeDynamic.prepare jest także trochę inna wersja – prepareAs – która zamiast MethodHandle zwraca obiekt implementujący podany interfejs funkcyjny (z 1-ną metodą). Wywołanie tej metody, wywołuje de facto metodę zawierającą instrukcję INVOKEDYNAMIC

Page 60: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

60

Klasa InvokeDynamic

• jej zrozumienie nie jest szczególnie trudne, ale trzeba mieć elementarną wiedzę o ASM– lub rozumieć wzorzec Visitor

• metody prywatne tej klasy po kolei tworzą strukturę nowej klasy: klasy „z jedną metodą, a w tej metodzie jest instrukcja INVOKEDYNAMIC”

• dostajemy wygenerowany ciąg bajtów (classfile) i za pomocą swojego classloader’a ładujemy tę klasę i udostępniamy ją dla naszego kodu Java, gdzie możemy wołać jej metodę

Page 61: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

61

HelloIndyWorld2

Przykład pokazuje, że do BSM można (z miejsca wywołania !) przekazywać parametry, a tym samym skonfigurować sposób wywoływania metod z tego miejsca– dynamizm w akcji !

Page 62: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

62

DemoIndySecond.HelloIndyWorld2public class HelloInDyWorld2 {

public static String myConfituraMethod(String s) { return s + " 2012"; }

public static String myConfituraMethodNext(String s) { return s + " 2013"; }

public static CallSite myBSM(MethodHandles.Lookup caller, String methodName, MethodType methodType, Object... bsmArgs) {MethodHandle mh = null;try {

if((bsmArgs != null) && (bsmArgs.length > 0) && ("2013".equals(bsmArgs[0])))mh = MethodHandles.lookup().findStatic(HelloInDyWorld2.class, "myConfituraMethodNext", MethodType.methodType(String.class, String.class));

elsemh = MethodHandles.lookup().findStatic(HelloInDyWorld2.class, "myConfituraMethod", MethodType.methodType(String.class, String.class));

} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }

return new ConstantCallSite(mh);}

public static void main(String args[]) throws Throwable {MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class),

"myBSM", HelloInDyWorld2.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class));

System.out.println(mh.invoke("Confitura"));

MethodHandle mh2 = InvokeDynamic.prepare("run me two", MethodType.methodType(String.class, String.class), "myBSM", HelloInDyWorld2.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class), "2013");

System.out.println( mh2.invoke("Confitura") );}

}

Page 63: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

63

HelloIndyWorld3

• przykład pokazuje jak uczynić kod korzystający z dynamicznego wywoływania metod bardziej „normalnym”

• zamiast uchwytów do metod, mamy obiekt na którym wywołujemy („normalnie” ) metodę interfejsu, który ten obiekt implementuje– ten interfejs, to tzw. functional interface

• http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html • w sumie to nie jedyny związek pomiędzy projektem „Java lambda

expressions” a invokedynamic…

Page 64: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

64

DemoIndySecond.HelloIndyWorld3public class HelloInDyWorld3 {

public interface IExecutable {public String execute(String s);

}

public static String myConfituraMethod(String s) { return s + " 2012"; }public static String myConfituraMethodNext(String s) { return s + " 2013"; }

public static CallSite myBSM(MethodHandles.Lookup caller, String methodName, MethodType methodType, Object... bsmArgs) {MethodHandle mh = null;try {

if((bsmArgs != null) && (bsmArgs.length > 0) && ("2013".equals(bsmArgs[0])))mh = MethodHandles.lookup().findStatic(HelloInDyWorld3.class, "myConfituraMethodNext",

MethodType.methodType(String.class, String.class));else

mh = MethodHandles.lookup().findStatic(HelloInDyWorld3.class, "myConfituraMethod", MethodType.methodType(String.class, String.class));

} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }

return new ConstantCallSite(mh);}

public static void main(String args[]) throws Throwable {IExecutable myObj = InvokeDynamic.prepareAs(IExecutable.class,

"run me", MethodType.methodType(String.class, String.class), "myBSM", HelloInDyWorld3.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class,

Object[].class));System.out.println( myObj.execute("Hello !") );

}}

Page 65: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

65

DEMO IIIczyli krótki benchmark wywołań metod:InvokeDynamic vs. Static vs. Reflection

Page 66: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

66

Po co taki (głupawy w sumie) benchmark ?

• bo dosyć często podnoszą się głosy, że taki dynamiczny mechanizm wywoływania metod musi być wolny – „jak pokazuje doświadczenie z refleksją…”

• niekoniecznie.– po to przede wszystkim stworzono InvokeDynamic

(JSR-292), aby pogodzić dynamizm z wydajnością• a przynajmniej dać JIT na to szansę

– z każdym kolejnym uaktualnieniem Java 7 spodziewałbym się też coraz większej wydajności InDy

Page 68: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

68

Benchmark• 7-krotne wywołanie 100 mln razy poniższej metody:

public static long sumAndMultiply(long a, long b, int multiplier) {return multiplier * (a + b);

}

• w sposób:1. dynamiczny (przy użyciu InvokeDynamic)2. statyczny3. refleksyjny4. refleksyjny bez autoboxing’u, czyli zamiast powyższej metody, wywoływana

jest:

public static Long sumAndMultiplyLong(Long a, Long b, Integer multiplier) {

return multiplier * (a + b);}

Page 69: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

69

Wyniki ?

Page 70: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

70

Benchmark INVOKE DYNAMIC1999800000000, TIME: 228 ms1999800000000, TIME: 150 ms1999800000000, TIME: 127 ms1999800000000, TIME: 132 ms1999800000000, TIME: 133 ms1999800000000, TIME: 140 ms1999800000000, TIME: 144 ms

Benchmark NORMAL (STATIC)1999800000000, TIME: 142 ms1999800000000, TIME: 154 ms1999800000000, TIME: 122 ms1999800000000, TIME: 135 ms1999800000000, TIME: 139 ms1999800000000, TIME: 131 ms1999800000000, TIME: 126 ms

Benchmark REFLECTIVE1999800000000, TIME: 4513 ms1999800000000, TIME: 4258 ms1999800000000, TIME: 4248 ms1999800000000, TIME: 4290 ms1999800000000, TIME: 4236 ms1999800000000, TIME: 4156 ms1999800000000, TIME: 4195 ms

Benchmark REFLECTIVE NO BOXING1999800000000, TIME: 3077 ms1999800000000, TIME: 2879 ms1999800000000, TIME: 2921 ms1999800000000, TIME: 3011 ms1999800000000, TIME: 2910 ms1999800000000, TIME: 3010 ms1999800000000, TIME: 2845 ms

Page 71: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

71

DEMO IVczyli przykład wykorzystania jednej z cech BSM

Page 72: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

72

Ale po co ?

• pewnie wciąż zadajecie sobie pytanie, po co ten cały InvokeDynamic ?

• odpowiedź wciąż brzmi: – aby pisać bardziej dynamiczny kod

• w tym przykładzie będzie pokazane wykorzystanie jednej z ważnych cech InvokeDynamic (i mojej również)– laziness

Page 73: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

73

BSM

• jak pamiętacie, BSM dla instrukcji INVOKEDYNAMIC jest wykonywana dopiero wtedy, gdy JVM taką instrukcję napotka

• czyli można ten fakt wykorzystać do tego, aby bardzo późno wykonywać pewne operacje (np. inicjalizacje) – „późno” = „dopiero gdy jest to potrzebne/używane”

Page 74: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

74

Przykład – Lazy Constant

• załóżmy, że chcecie mieć w swojej klasie stałą wartość (np. XML), ale inicjowaną poprzez wykonanie jakiejś bardziej złożonej logiki– załóżmy, że ta logika potencjalnie może

wykonywać się długo• a w Waszej aplikacji czas jest istotny

Page 75: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

75

Pomysł 1Stała na poziomie klasy, inicjowana w bloku statycznympublic class ConstantResourceImpl implements ConstantResource {

public static final String xml;static {

xml = ParseHelper.doSomeHeavyParsing("test.xml");}

@Overridepublic void notNeedingTheXMLHere() {

//Hey, I am NOT using the 'xml' constant here !}

@Overridepublic void badlyNeedingTheXMLHere() {

//I will be using the 'xml' constant here !try {

System.out.println(xml);} catch (Throwable e) { e.printStackTrace(); }

}}

Page 76: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

76

Zadziała ?LazyConstantsMain.java

Zadziała, ale inicjalizacja tej stałej (czyli wykonanie tego mega-złożonego parsowania) wykona się owszem raz, ale niezależnie od tego, czy tej stałej w ogóle użyjemy czy też nie (czyli parsowanie wykona się zawsze).

public class LazyConstantsMain {public static void main(String[] args) {

ConstantResource testObject = new ConstantResourceImpl();testObject.notNeedingTheXMLHere();

}}

Page 77: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

77

Pomysł 2 (bardziej lazy, a w zasadzie to very lazy)

• „dostęp do wartości stałej” = „wykonanie metody zwracającej (stałą) wartość”– patrz: MethodHandles.constant(T, v)

• niech inicjalizacja tej stałej odbywa się w BSM– czyli wykona się co najwyżej 1 raz– i tylko wtedy, gdy rzeczywiście jakiś kod odczytuje wartość tej stałej

• ponieważ wszystko jest stałe (constant MethodHandle, ConstantCallSite), to jest bardzo wysoka szansa, że JIT zaaplikuje wszystko co ma najlepsze – optymalizacje typu inlining, constant folding, etc.

• czyli nic nie tracimy, a zyskujemy laziness– a nawet pewien dodatkowy dynamizm – bo, to co (i/lub jak) jest

parsowane może być określone dynamicznie (= w czasie wykonania)– dodatkowo: podejście z InDy jest bardziej bezpieczne wątkowo ! Patrz

gwarancje określone w JVM spec.

Page 78: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

78

public class ConstantResourceImplWithIndy implements ConstantResource {private MethodHandle mh;

public static CallSite bootstrapForCallMe(MethodHandles.Lookup callerClass, String name,java.lang.invoke.MethodType type, Object... args ) {

String xml = ParseHelper.doSomeHeavyParsing((String) args[0]);return new ConstantCallSite( MethodHandles.constant( String.class, xml ) );

}

public ConstantResourceImplWithIndy(String resourceName) {try {

mh = InvokeDynamic.prepare("callMe", MethodType.methodType(String.class, new Class<?>[] {}), "bootstrapForCallMe", ConstantResourceImplWithIndy.class, MethodType.methodType(CallSite.class, Lookup.class, String.class,

MethodType.class, Object[].class), resourceName );

} catch (Throwable e) { e.printStackTrace(); }}

@Overridepublic void notNeedingTheXMLHere() { //But, I am NOT using the 'xml' constant here ! }

@Overridepublic void badlyNeedingTheXMLHere() { //I will be using the 'xml' constant here !

try { System.out.println( mh.invoke()); } catch (Throwable e) { e.printStackTrace(); }}

}

Page 79: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

79

Zadziała ?LazyConstantsMainWithIndy.java

Zadziała !public class LazyConstantsMainWithIndy {

public static void main(String[] args) {ConstantResource testObject = new ConstantResourceImplWithIndy( "test.xml" );testObject.notNeedingTheXMLHere();

}}

Dopóki nie użyjemy tej stałej, nie jest ona inicjalizowana i mega-złożone parsowanie nie odbywa się.

Page 80: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

80

A gdy użyjemy stałej…

public class LazyConstantsMainWithIndy {

public static void main(String[] args) {ConstantResource testObject = new ConstantResourceImplWithIndy("test.xml");testObject.badlyNeedingTheXMLHere();

}}

Page 81: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

81

Ten sam trick może być użyteczny w wielu Waszych programach…

Page 82: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

82

Github

• DemoIndyFourthhttps://github.com/waldekkot/Confitura2012_InvokeDynamic/tree/master/DemoIndyFourth

Page 83: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

83

DEMO Vczyli przyspiesz swój kod rekurencyjny

(a tak naprawdę, to: jak czerpać intelektualną przyjemność z czytania kodu zawierającego InvokeDynamic)(zwłaszcza, gdy tego kodu nie musisz napisać )

Page 84: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

84

Czy masz pomysł jak przyspieszyć ten kod ?Klasyka: obliczanie sumy N pierwszych liczb z ciągu Fibonacciego

public class FibonacciSum {private static final long HOW_MANY_NUMBERS = 40;public static long fib(long n) {

if (n == 0) return 0;if (n == 1) return 1;return fib(n - 1) + fib(n - 2);

}

public static void main(String[] args) {System.out.println("SUM OF FIRST " + HOW_MANY_NUMBERS + " NUMBERS OF FIBONACCI SEQ …");long start = System.currentTimeMillis();long result = 0;for (long i = 0; i < HOW_MANY_NUMBERS; i++)

result += fib(i);System.out.println("TIME: " + (System.currentTimeMillis() - start) + " ms");System.out.println("RESULT: " + new DecimalFormat("###,###,###,###,###").format(result));

}}

Page 85: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

85

Dlaczego ten kod działa wolno ?

• ten kod jest piękny – bardzo przejrzyście wyraża intencję programisty

• ale (w większości języków) jest nieefektywny– nie z powodu rekurencji…– ale dlatego, że te same obliczenia są wykonywane

wielokrotnie• i co gorsza zapominane…

Page 86: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

86

Memoization

• wykorzystamy dynamiczne wywoływanie metod, aby ten piękny i przejrzysty kod przyspieszyć– nie naruszając jego piękna

• zastosujemy coś, co w literaturze IT nazywa się ‚memoization’, czyli zapamiętywanie wyników funkcji i unikanie wykonywania ich powtórnie. W skrócie:– zapamiętujemy wynik wykonania funkcji dla określonych argumentów– przed wykonaniem funkcji, sprawdzamy, czy funkcja z takimi

argumentami była już wykonywana– jeśli tak, nie wykonujemy funkcji, tylko zwracamy zapamiętany wynik– jeśli nie, wykonujemy funkcję, a jej wynik (i argumenty)

zapamiętujemy

Page 87: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

87

InvokeDynamic a memoization

InvokeDynamic dostarcza nam kilku ważnych elementów• w dalszej części prezentacji po kolei je omówię oraz

wyjaśnię jak zostały one razem złożone dla osiągnięcia przyspieszenia o które nam chodzi

• ten przykład to jest kod, który ukradłem z:– http://

code.google.com/p/jsr292-cookbook/source/browse/trunk/memoize/src/jsr292/cookbook/memoize/

• mój drobny wkład polega na tym, że ten genialny kod zrozumiałem i dzielę się moimi wrażeniami z innymi

Page 88: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

88

Ale zanim nastąpią wyjaśnienia, zobaczcie efekt końcowy !

SpeedRecurenceWithIndy.java

Page 89: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

89

Zresztą, w zasadzie to nawet zmieniliśmy złożoność obliczeniową

tej metody![z wykładniczej na niemal stałą ;-)]

Dla N > 102 kończy się pojemność long (263 -1) …

Zamieniając long na BigInteger można spokojnie podać i N = 3000 (ponad 600 cyfrowa liczba). Czas: ~50 ms . Ten przykład też jest na GitHub-ie.

Page 90: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

90

OK, to na czym polegają różnice ?

Page 91: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

91

1. Zamiast wywołań fib(n) użycie InvokeDynamicpublic class SpeedRecurenceWithIndy {

private static MethodHandle mhDynamic = null;

public static long fib(long n) throws Throwable {if (n == 0) return 0;if (n == 1) return 1;return (long) mhDynamic.invokeExact(n-1) + (long) mhDynamic.invokeExact(n-2); // return fib(n-1)+fib(n-2)

}[...]

public static void main(String args[]) throws Throwable {System.out.println("SUM OF FIRST " + HOW_MANY_NUMBERS + " FIBONACCI USING INVOKE DYNAMIC !");

MethodType bsmType = MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Class.class);

mhDynamic = InvokeDynamic.prepare("fib", MethodType.methodType(long.class, long.class),"myBSM", SpeedRecurenceWithIndy.class, bsmType,SpeedRecurenceWithIndy.class);

long start = System.currentTimeMillis();long result = 0L;for (long i = 0; i < HOW_MANY_NUMBERS; i++)

result += (long) mhDynamic.invokeExact(i);System.out.println("TIME: " + (System.currentTimeMillis() - start) + " ms");System.out.println("RESULT: " + new DecimalFormat("###,###,###,###,###").format(result));

}}

Page 92: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

92

2. Bufor do przechowywania wynikówprivate static ClassValue<HashMap<String, HashMap<Object, Object>>> cacheTables = new ClassValue<HashMap<String,

HashMap<Object, Object>>>() {@Overrideprotected HashMap<String, HashMap<Object, Object>> computeValue(Class<?> type) {

return new HashMap<String, HashMap<Object, Object>>();}

};

• oprócz uchwytów do metod i nowej instrukcji bytecode, InvokeDynamic dodaje także mechanizm pozwalający na przypisanie wartości (obiektu) do dowolnej klasy• http://docs.oracle.com/javase/7/docs/api/java/lang/ClassValue.html • to trochę taki odpowiednik ThreadLocal, tyle, że zamiast z wątkiem to wartość jest

związywana z klasą (Class<?>)• w przykładzie, ClassValue pełni rolę bufora wyników wywołania metod

• tu sprawdzamy, czy dla danego argumentu jest dostępny wynik• tu zapisujemy wynik wywołania metody• trzypoziomowa struktura:

1. klasa (tu: SpeedRecurrenceWithIndy)2. metoda (tu: fib)3. argumenty metody (klucz)

• wynik metody (wartość)Notabene: szkoda, że dla klas anonimowych nie można użyć nowego w Java 7 operatora diamond. Fajniej byłoby napisać: „= new ClassValue<>()”, a kompilator się domyślił reszty…

Page 93: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

93

3. Metody pomocnicze dla BSM (i ich uchwyty)

public static boolean notNull(Object receiver) { return receiver != null; }

public static Object update(HashMap<Object, Object> cache, Object result, Object arg) {cache.put(arg, result);return result;

}

private static final MethodHandle NOT_NULL;private static final MethodHandle MAP_GET;private static final MethodHandle UPDATE;static {

Lookup lookup = MethodHandles.lookup();try {

NOT_NULL = lookup.findStatic(SpeedRecurenceWithIndy.class, "notNull", MethodType.methodType(boolean.class, Object.class));

MAP_GET = lookup.findVirtual(HashMap.class, "get", MethodType.methodType(Object.class, Object.class));

UPDATE = lookup.findStatic(SpeedRecurenceWithIndy.class, "update",MethodType.methodType(Object.class, HashMap.class, Object.class, Object.class));

} catch (ReflectiveOperationException e) { throw (AssertionError) new AssertionError().initCause(e); }}

Rzeczy typu:• NOT_NULL: sprawdzenie czy podany obiekt nie jest null• MAP_GET, UPDATE: get i update (put) do HashMap’y (czyli bufora wyników)

Page 94: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

94

4. Bootstrap method (I)

public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws ReflectiveOperationException {

MethodHandle mh = null;try {

mh = MethodHandles.lookup().findStatic(SpeedRecurenceWithIndy.class, "fib", type);} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }

return new ConstantCallSite(mh);}

Aaaaaaby zamienić statyczne wywołania [ fib() ] na dynamiczne, wystarczyłoby tyle:

My chcemy jednak „wpiąć się” z bardziej złożoną logiką !

Page 95: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

95

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

WTF !?!

Spokojnie, będę po kolei całość wyjaśniać !

Poznając InvokeDynamic warto poznać jeden trick :

TRZEBA CZYTAĆ OD KOŃCA (czyli od dołu w górę) !!!

Page 96: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

96

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

Dla przypomnienia: zadaniem BSM jest utworzyć i skonfigurować miejsce wywołania (callsite). Skonfigurować, czyli określić docelową metodę – target.

Tutaj, utworzone miejsce wywołania, tj. jego target, mimo, iż będzie to całkiem skomplikowany łańcuch operacji, nie będzie się zmieniał, więc jest typu ConstantCallSite.

A to sprzyja optymalizacjom wykonywanym przez JIT.

Łańcuch kombinatorów musi zapewnić, że typ uchwytu zwracanego z BSM (tu: memoize) będzie zgodny z typem miejsca wywołania (czyli w tym przykładzie: (long)long, bo taki jest typ metody fib).Ale ten BSM jest ładniejszy (bardziej generyczny) – nie ma „zaszytych” tych typów – stąd operacje w rodzaju type.parameterType(0), type.returnType(), itp.

Page 97: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

97

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

Argumentami foldArguments są uchwyty do metod. foldArguments działa w ten sposób, że:1. najpierw wywołuje uchwyt podany jako drugi argument (tu: cacheQuerier)2. jeśli zwróci on wynik (także null, ale nie void), to ten wynik jest wstawiany

(INSERT !) do listy argumentów na pozycji 0 pierwszego uchwytu (tu: combiner)

3. wywoływany jest pierwszy uchwyt (tu: combiner)

Tutaj, cacheQuerier będzie najpierw odpytywał bufor (za pomocą metody HashMap.get) i wstawiał wynik tego odpytywania (obiekt/wynik obliczeń lub null).

Typ wynikowego uchwytu z foldArguments (tu: memoize) musi być zgodny z typem miejsca wywołania [tu: (long)long, bo taki jest typ metody fib]

Page 98: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

98

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

Zadaniem cacheQuerier jest odpytanie bufora (choć nie jestem przekonany czy ‚cache’ to jest tutaj właściwa nazwa), czy dla danego argumentu (wartości przekazanej do metody fib), który tutaj jest kluczem, została wcześniej przypisana wartość (rezultat wcześniejszego wywołania).Jeśli nie, to zwracany jest null.Jeśli tak, to zwracany jest obiekt (=wstawiony wcześniej, wynik wykonania metody).

Wracając trochę do poprzedniego slajdu i metody foldArguments:Uchwyt cacheQuerier w czasie wykonania (!) zawsze zwróci wynik różny od void (bo tak działa HashMap.get), więc jeśli chodzi o typowanie, to pierwszy argument uchwytu combiner będzie pominięty (tak działa foldArguments). Czyli (tu) typ uchwytu combiner musi być (Object, long)long [bo Object będzie pominięty), czyli de facto (long)long, a zatem będzie zgodny z wymaganiem miejsca wywołania (taki typ musi mieć memoize).Kontynuując: cacheQuerier musi mieć typ (Object)long [bo taki jest wymóg foldArguments). cacheQuerier to uchwyt do metody HashMap.get, która ma typ (Object)Object. A zatem, za pomocą metody asType, adaptujemy uchwyt cacheQuerier, tak, aby spełnić wymóg foldArguments i „zaprezentować” cacheQuerier, jako uchwyt o typie: (Object)long.

Gra i buczy…

Page 99: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

99

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

cacheQuerier może zwrócić null, co oznacza, że nie znaleziono wartości w buforze wyników.

guardWithTest działa jak konstrukcja if-then-else. Jego argumentami są uchwyty do metod. Pierwszy argument, to test; jeśli zwraca on true, to wykonywany jest uchwyt podany jako drugi argument. Jeśli zwraca false, to wykonywany jest uchwyt podany jako trzeci argument.

Tutaj test (czyli uchwyt NOT_NULL, patrz metoda pomocnicza notNull) operuje na tym co zwrócił cacheQuerier. Jeśli był to null, to wykonywany jest fallback, który wywoła oryginalną metodę [fib(n)], a następnie wstawi jej wynik do bufora. Jeśli z bufora zwrócono jakiś obiekt, to wykonywany jest identity, który zwróci ten obiekt (wynik), jako wynik wywołania metody [fib].

Page 100: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

100

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

Chyba łatwiej jest najpierw prześledzić działanie fallback, czyli sytuacji, gdy w buforze nie znaleziono wyniku.

Uchwyt fallback powinien wykonać oryginalną metodę (target) [czyli tu: fib(n) ], a następnie jej wynik umieścić w buforze. Do tego celu użyto ponownie metody foldArguments. Najpierw wywoływany jest target (fib), a jego wynik (który w tym przykładzie zawsze będzie różny od void, bo fib zwraca long) jest wstawiany na pozycję 0 listy argumentów dla uchwytu update (patrz metoda pomocnicza update).To będzie na następnym slajdzie, ale po wstawieniu do bufora (czyli wykonaniu HashMap.put), update zwraca wstawioną wartość (tak działa HashMap.put) i ta wartość będzie wynikiem wywołania całego invokedynamic.

Trzeba jeszcze dopasować typy. Uchwyt combiner jest typu (Object, long)long i taki też jest typ fallback w guardWithTest. Zanim wywołamy target [ który jest typu (long)long ], za pomocą dropArguments pozbywamy się tego nadmiarowego argumentu typu Object (który i tak na tym etapie jest równy null).

Page 101: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

101

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

Oryginalna metoda (tu: fib) jest określona przez uchwyt target.

Warto zwrócić uwagę, że w tym przypadku do BSM przekazywana jest klasa, w której ta metoda się znajduje (w parametrze: staticType). Kod z main():mhDynamic = InvokeDynamic.prepare("fib", MethodType.methodType(long.class, long.class),

"myBSM", SpeedRecurenceWithIndy.class, bsmType,SpeedRecurenceWithIndy.class);

Typ target to: (long)long

Page 102: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

102

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

Po wykonaniu target (czyli fib), wywoływany jest uchwyt update. Jego zadaniem jest umieścić wynik z wywołania target w buforze (za pomocą HashMap.put – patrz metoda pomocnicza update).

Musimy zapewnić zgodność typów. Metoda update jest typu: (Object, Object)Object (bo taki jest typ HashMap.put, który wykonywany jest w metodzie pomocniczej update. Notabene – ponieważ robimy bindTo, to pierwszy argument metody update – cache - jest „ustawiony” na obiekt cache). Ale metoda update wywoływana w kontekście foldArguments powinna mieć typ (long, long)long [bo target ma typ (long)long, czyli zwraca long i wstawia go do listy argumentów update]. A zatem, za pomocą metody asType (i insertParameterTypes) dopasowujemy stosownie uchwyt update (HashMap.put), tak, aby był typu (long, long)long.

Page 103: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

103

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

Drugi przypadek, to ten, w którym cacheQuerier znalazł wynik w buforze dla podanego argumentu (czyli to co zwrócił nie było równe null). Wtedy wykonywany jest uchwyt identity.

Typ identity musi być: (Object,long)long [ bo taki musi być typ combiner, którego typ – po odrzuceniu Object – musi być zgodny z typem zgłoszonym w callsite, czyli: (long)long ].

Page 104: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

104

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

W zasadzie to tu chodzi o to, żeby zwrócić wartość otrzymaną z buforu wyników, ale trzeba wykonać małe mambo-jumbo z argumentami.

Ostatecznie typ identity musi być: (Object, long)long (bo tak w tym przykładzie wymaga guardWithTest). Najpierw za pomocą MethodHandles.identity tworzymy uchwyt, który zwraca to co się do niego przekaże (=identity) i określamy typ wartości które on zwraca (na taki jak target, tu: long). Czyli jego typ jest: (long)long. Ale ponieważ to co będziemy przekazywać do identity (czyli wartość z buforu wyników) jest typu Object, to za pomocą asType dopasowujemy uchwyt identity, tak, żeby on był: (Object)long.

Za pomocą dropArguments pozbywamy się argumentu oryginalnej metody [tj. „n” w fib(n)] – ten argument ma typ long. (nie potrzebujemy go już, bo mamy przecież wynik z buforu wyników – typu Object). W sumie ten łańcuch operacji daje nam typ: (Object, long)long, czyli znowu gra i buczy.

Page 105: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

105

4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws

ReflectiveOperationException {MethodHandle target = lookup.findStatic(staticType, name, type);

HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);

String selector = name + type.toMethodDescriptorString();HashMap<Object, Object> cache = cacheTable.get(selector);if (cache == null) {

cache = new HashMap<Object, Object>(); cacheTable.put(selector, cache);

}

MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));

MethodHandle update = UPDATE.bindTo(cache);update = update.asType(type.insertParameterTypes(0, type.returnType()));

MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class);

MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);

MethodHandle cacheQuerier = MAP_GET.bindTo(cache);cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);return new ConstantCallSite(memoize);

}

Z kodu w BSM pozostał jeszcze kod inicjalizujący bufor wyników.

Bufor (HashMap) trzyma wyniki [ =fib(n) ] dla określonych argumentów [ n ].

W zasadzie to nie utrzymujemy jednego bufora, ale potencjalnie wiele buforów: „per metoda” (określona przez jej nazwę i typ – patrz selector) i „per klasa” w której ta metoda jest zdefiniowana. Stąd, trzy poziomowa struktura:ClassValue<HashMap<String, HashMap<Object, Object>>>

ClassValue to w zasadzie też HashMap (dokładniej WeakHashMap), tyle tylko, że trochę bardziej „inteligentna” i dopasowana do roli buforu dla wartości związanych z Class.

Page 106: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

106

I tyle…

• po rozłożeniu na kawałki ten kod nie wydaje się wcale taki skomplikowany– i można go zastosować do niemal dowolnego kodu

• memoize uważam za świetny przykład mocy jaką daje InvokeDynamic– wiele radości można też doznać czytając pozostałe

przykłady: http://code.google.com/p/jsr292-cookbook

• Kod demo (projekt DemoFifth) na GitHub:https://github.com/waldekkot/Confitura2012_InvokeDynamic/tree/master/DemoFifth

Page 107: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

107

Podsumowanie• InvokeDynamic pozwala nam mieć kontrolę nad wywoływaniem metod

– „żeby łatwiej i wydajniej pisać dynamiczny kod”– a zatem nie tylko nisza: „języki dynamiczne na JVM” !

• API dostarcza sporo ciekawej maszynerii– uchwyty do metod (MethodHandle)– kombinatory – INVOKEDYNAMIC - instrukcję bytecode’u dynamicznego wywołania metody– obiekty callsite (constant, mutable, volatile)– ClassValue, SwitchPoint

• i w dodatku jest ona (oraz intencje programisty) lepiej rozumiana przez JVM (JIT)=> lepsza wydajność

• sprytne połączenie tych elementów pozwala tworzyć ładny, bardziej generyczny i dynamiczny kod– szczególnie w połączeniu z technikami takimi jak AOP (co słusznie podczas wykładu zauważył

Koziołek (http://koziolekweb.pl) . Notabene AspectJ 1.7 powoli zaczyna już wspierać InvokeDynamic.

Page 108: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

108

Warte odwiedzania

• https://blogs.oracle.com/jrose/• http://blog.headius.com• http://www.java.net/blogs/forax/• http://code.google.com/p/jsr292-cookbook• http://groups.google.com/group/jvm-languages/

Page 109: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

109

Potencjalne usprawnienia…

Java 8 ma przynieść JSR292.next i mówi się np. o takich rzeczach jak:• anonimowe classloader’y (znacznie ułatwią i uczynią bardziej

wydajnym dla GC dynamiczne generowanie klas i metod, w tym także API do modyfikacji constant pool – będzie można sobie dynamicznie patchować klasy – juhoooo!)

• Fixnums, czy zmniejszona presja na autoboxing, poprzez zawarcie wartości liczby w samej referencji

• Interface injection, czyli dynamiczne (w czasie wykonania) dodawanie metod do klas/instancji

• Coroutines, czyli uogólnienie subroutines, czyli metody z więcej niż 1 punktem wejścia (m.in. mielibyśmy prostszy kod współbieżny)

• bardzo chciałbym, żeby chociaż coś z tego wyszło…

Page 110: INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

110

Dzięki !

Waldek [email protected]