SlideShare une entreprise Scribd logo
1  sur  110
INVOKEDYNAMIC = bardziej dynamiczna JVM

                          Confitura 2012



                             Waldek Kot
                        waldek.kot@gmail.com



wersja 1.1                                             1
Prezentacja mocno korzysta z
animacji, więc warto się przełączyd
      na tryb „Slide Show”




                                      2
Prolog
Niniejsza prezentacja zawiera materiał, który chciałem
zaprezentowad 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 zobaczyd co dzieje się na
   ekranie (tj. rzędom od 4-go w górę na pewno nie)

   … a przynajmniej tak to sobie tłumaczę 

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




                                       4
Motywacja
czyli dlaczego zgłosiłem taki temat na Confiturę 2012 ?




                                                          5
Motywacja pozytywna


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




                                             6
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 powiedzied, że refleksja w Javie jest użyteczna tylko dla
      wybraocó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” 

                                                                                         7
Dlaczego warto poznad 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 zrozumied 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ę wspomnied 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ówid wyłącznie o wykorzystaniu InDy w języku Java.                            8
Po co InvokeDynamic ?

Żeby łatwiej tworzyd 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




                                                      9
Po co InvokeDynamic ?

• … ale: wszystko co daje InDy można dzisiaj zrobid
  innymi metodami („zasymulowad”)
• prawda, tyle tylko, że dzięki InDy:
  – będzie prościej, bo… nie musimy tego robid
     • „Najlepszy kod to taki którego nie trzeba pisad”
  – będzie wydajniej, bo:
     • kod z InDy jest lepiej „rozumiany” przez kompilator JIT JVM
     => JIT będzie w stanie wykonad o wiele więcej optymalizacji,
     niż przy zastosowaniu „symulatorów”

                                                             10
Ważna uwaga
• większośd przykładów i kodu, który tutaj pokazuję ukradłem 
   – adaptacja filozofii: „najlepszy kod, to taki, którego nie trzeba pisad”
• mój mały wkład tutaj polega na tym, aby:
   1. „odczarowad” temat InvokeDynamic
   2. poprzez przykłady - czasem trywialne - zachęcid do samodzielnego
       poznawania InDy
   3. ułatwid czytanie ze zrozumieniem kodu wykorzystującego InDy,
       dostępnego w sieci
      • i czerpad z tego taką przyjemnośd, jaką ja miałem (i mam nadal)
      • chodby 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/                                        11
Krótka teoria InvokeDynamic




                              12
InvokeDynamic


Pozwala programiście mied kontrolę nad tak
czymś tak elementarnym, jak wywoływanie
                 metod




                                             13
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ą .




                                                      14
MethodHandle - Hello World
• do zabawy z większością tego co oferuje Indy
  wystarczy Java 7 i IDE
• czyli tyle wystarczy rozumied, aby zacząd :

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


                                                   15
MethodHandle – Hello World
                                                                    Do tworzenia uchwytów do metod używamy
                                                                    m.in. metod (statycznych) klasy
package pl.confitura.invokedynamic;                                 MethodHandles.

                                                                    Śmiesznie, ale w tym przykładzie tworzymy
                                                                    uchwyt do nieistniejącej metody !
import java.lang.invoke.MethodHandle;                               constant zawsze zwraca stałą wartośd, tu:
import java.lang.invoke.MethodHandles;                              typu String, równą „Hello World !”.
                                                                    Metoda invoke wywołuje metodę na którą
                                                                    wskazuje uchwyt.
public class HelloIndyWorld {
            public static void main(String[] args) throws Throwable {
                        MethodHandle mh = MethodHandles.constant(String.class, "Hello World !");
                        System.out.println(mh.invoke());
            }
}




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




                              17
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
                                                                       18
           referencyjnych.
MethodHandle - kombinatory
• API udostępnione w ramach InvokeDynamic pozwala na
  całkiem złożone „manipulacje” uchwytami do metod
     – możemy dostawad się do różnego rodzaju metod (statycznych,
       wirtualnych, konstruktorów, itp.), a także w szerokim zakresie
       manipulowad 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 (łaocuch) 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 aplikowad
       swoje optymalizacje np. method inlining
                                                                                               19
     – JIT może taki graf „przejśd”, węzeł po węźle i „zrozumied” go
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 byd silnie
  typizowalna (strong typing)
   – a zatem te z optymalizacji JIT, które korzystają z informacji o typach,
     mogą byd 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 poznad, 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 znad dopiero w czasie
            wykonania (kompilator daje tu niewiele)
   2.       JVM jest absolutnie bezwzględny jeśli chodzi o zgodnośd typów !
        •      bezpieczeostwo i szybkośd
                                                                               20
Nowy bytecode: INVOKEDYNAMIC
Pierwsze w historii rozszerzenie listy instrukcji JVM !
• chod można spekulowad, ż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śd
    wpływu programisty na sposób wywoływania metod
                                                           21
Bytecode: INVOKEDYNAMIC
• przy pierwszym napotkaniu tej instrukcji, JVM wywołuje metodę (tzw.
  BSM – bootstrap method), której zadaniem jest określid 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)

                                                                        22
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 zmodyfikowad to „miejsce”, tj.
       podstawid tam inny uchwyt
        – dynamizm InDy w akcji !

                                                                  23
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 aplikowad te ze swoich
        optymalizacji, które zakładają, że zawsze będzie wywoływana ta sama
        metoda (czyli należy oczekiwad, że szybkośd 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 tworzyd swoje własne specjalizacje klas CallSite !
   – z własną logiką, stanem, itp.

                                                                       24
Inne przydatne konstrukcje
API InvokeDynamic udostępnia jeszcze kilka innych przydatnych konstrukcji
pomocniczych (prostszych i/lub bardziej wydajnych niż gdyby samodzielnie
budowad ich odpowiedniki):
• java.lang.ClassValue<T> – pozwala programiście przypisad 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żyd
• SwitchPoint – rodzaj semafora, który bezpiecznie wątkowo może
  poinformowad o pewnej zmianie związane z nim uchwyty
    – będzie później przykład praktyczny, który to lepiej objaśni
• MethodHandleProxies – pozwala utworzyd 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) 


                                                                                25
Czas na DEMO !
(wiem, nareszcie )




                      26
DEMO I
czyli zabawy z MethodHandle




                              27
Co potrzeba ?
• Java 7
  – im wyższa wersja, tym lepszej wydajności
    InvokeDynamic należy się spodziewad
• IDE
  – tworzymy zwykły projekt Java




                                               28
MethodHandle – Hello World (było)
                                                                  Do tworzenia uchwytów do metod używamy
                                                                  m.in. metod (statycznych) klasy MethodHandles.
package pl.confitura.invokedynamic;
                                                                  Śmiesznie, ale w tym przykładzie tworzymy
                                                                  uchwyt do nieistniejącej metody !
                                                                  Constant zawsze zwraca stałą wartośd, tu: typu
import java.lang.invoke.MethodHandle;                             String, równą „Hello World !”.
import java.lang.invoke.MethodHandles;                            Metoda invoke wywołuje metodę na którą
                                                                  wskazuje uchwyt.

public class HelloIndyWorld {
            public static void main(String[] args) throws Throwable {
                        MethodHandle mh = MethodHandles.constant(String.class, "Hello World !");
                        System.out.println(mh.invoke());
            }
}




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



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 !"));
            }
}                                                  To jest argument do uchwytu do metody
                                                  (tu: mh). Te argumenty będą przekazane
                                                  do metody na którą wskazuje uchwyt (tu:
                                                  identity).




                                                                                                                    30
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ślid typ metody, czyli jakiego
}                                                          typu są parametry i wynik podanej
                                                                 metody.
                                                                 Więcej informacji: klasa MethodType w
                                                                 java.lang.invoke




                                                                                                         31
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
                                                                    • invokeWithArguments
                                                                    Invoke dokonuje opcjonalnie konwersji argumentów i
                                                                    wyniku do tego co oczekuje wywołujący (caller).
                                                                    InvokeExact jest szybszy, ale wymagana jest 100%
                                                                    zgodnośd 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.




                                                                                                                      32
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 …                                                           33
MethodHandle – coś ciekawszego: uchwyt do własnej metody
public class HelloIndyWorld {                                                       Lookup pozwala uzyskad uchwyt do
              public static class MyClass {                                         różnorodnych metod, np. tutaj do metody
                                                                                    statycznej myConcat w klasie MyClass.
                            static public String myConcat(String s1, String s2) {
                                          return s1 + s2;                           W 100% muszą zgadzad się nie tylko nazwa
                            }                                                       metody, ale także jej sygnatura (określona
                                                                                    przez typ metody – MethodType [mt])
              }

             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"));
             }
}




                                                                                                                     34
MethodHandle – uchwyt do metody wirtualnej
public class HelloIndyWorld {
               public static class MyClass {
                                private String s;
                                                                                    Metody wirtualne klasy wyszukujemy za pomocą
                                public MyClass(String s) {                          findVirtual.
                                              this.s = s;
                                                                                    W metodach wirtualnych, implicite, ich pierwszy
                                }                                                   argument określa obiekt na którym ta metoda
                                                                                    będzie wykonana (receiver).
                                public int howManyCharacters(String s) {            Za pomocą bindTo możemy utworzyd uchwyt do
                                              return (s + this.s).length();         metody, w którym ten argument (receiver) będzie
                                }                                                   na stałe ustawiony na wybrany obiekt.
                }

                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"));
                }
}




                                                                                                                            35
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…




                                                                                                                   36
MethodHandle – składanie uchwytów (kombinatory)
public class HelloIndyWorld {
               public static class MyClass {
                                private String s;                                            Przykład utworzenia nowego uchwytu do
                                                                                             metody – za pomocą metody
                                public MyClass(String s) {                                   filterArgument. Najpierw na podanym
                                              this.s = s;                                    argumencie (0, czyli „ma kota”), wykona się
                                }                                                            metoda wskazywana przez uchwyt
                                                                                             mhToUpper (czyli metoda wirtualna
                                public String myVirtualConcat(String s) {                    „toUpperCase” dla obiektu klasy String).
                                               return this.s + s;                            Zwrócony z niej wynik (czyli „MA KOTA”)
                                }                                                            będzie nowym argumentem dla mh
                }                                                                            (pierwszy argument w filterArguments).

                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"));
                }
}




                                                                                                                                 37
MethodHandle – filtrowanie wyników metod
public class HelloIndyWorld {
               public static class MyClass {
                                private String s;

                                public MyClass(String s) {                                   Tu najpierw wykonywana jest metoda na
                                              this.s = s;                                    którą wskazuje mh (czyli myVirtualConcat),
                                }                                                            a następnie na jej wyniku (czyli „Ala ma
                                                                                             kota”) wykonywana jest metoda na którą
                                public String myVirtualConcat(String s) {                    wskazuje mhToUpper.
                                               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"));
                }
}




                                                                                                                                 38
MethodHandle – interceptor typu ‚before’
public class HelloIndyWorld {
      public static class MyClass {
                private String s;
                public MyClass(String s) {                                             foldArguments działa w ten sposób, że najpierw
                                this.s = s;                                            wykonywany jest drugi argument (mhInterceptor), a
                }                                                                      jego wynik (o ile nie jest void) jest WSTAWIANY
                                                                                       (INSERT) jako argument do wywołania pierwszego
                public String myVirtualConcat(String s) {                              argumentu (mh).Po czym wywoływany jest mh.
                               return this.s + s;                                      Czyli poniższy kod nie zadziała. Dlaczego ? Bo po
                }                                                                      wstawieniu dodatkowego argumentu (czyli wyniku z
                                                                                       mhInterceptor) nie zgadzają się typy uchwytów w
                public static String myInterceptor(String s) {                         foldArguments (a muszą one byd takie same).
                                System.out.println("Intercepted, with arg s: " + s);   Komunikat błędu jest przy tym dosyd mylący …
                                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"));
      }
}



                                                                                                                                    39
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) {                              Za pomocą metody dropArguments trzeba pozbyd się
                               return this.s + s;                                      niepotrzebnego już argumentu (czyli „ma kota”).
                }                                                                      Pozycja argumentów liczona jest od 0, ale ponieważ
                                                                                       wynik wywołania mhInterceptor jest WSTAWIANY na
                public static String myInterceptor(String s) {                         pozycję 0, to oryginalne argumenty przesuwają się (czyli
                                System.out.println("Intercepted, with arg s: " + s);   „ma kota” jest teraz na pozycji 1).
                                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"));
      }
}


                                                                                                                                       40
MethodHandle – interceptor typu ‚before’ (najpoprawniej )
public class HelloIndyWorld {
      public static class MyClass {
                private String s;
                public MyClass(String s) {
                                this.s = s;
                }                                                                      Bo tak jest bardziej generycznie. Typ usuwanego
                                                                                       argumentu jest zgodny z typem parametru metody
                public String myVirtualConcat(String s) {                              myVirtualConcat). Dla wyjaśnienia:
                               return this.s + s;                                      • typ mh to: (String)String
                }                                                                      • mh.type().parameterType(0) odnosi się do
                                                                                          pierwszego (i tu jedynego) argumentu metody
                public static String myInterceptor(String s) {                            myVirtualConcat, sprzed wstawienia wyniku z
                                System.out.println("Intercepted, with arg s: " + s);      interceptora
                                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"));
      }
}
                                                                                                                                   41
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"));
      }
}


                                                                                                                                 42
MethodHandles / MethodHandle
Dostępne są także metody:
• wstawiające argumenty wybranego typu (insertArgument)
• zmieniające kolejnośd 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)
• …

                                                               43
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




                                                                                    44
MethodHandle – przykład z .guardWithTest
public 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"));
               }
}

                                                                                                                          45
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"));
           }
}




                                                                                                46
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




                                                     47
MethodHandleProxies - przykład
public 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") );
            }
}




                                                                                                                48
DEMO II
czyli wywołanie instrukcji bytecode’u
         INVOKEDYNAMIC




                                        49
Mały problem
• niestety, ale w Java 7, w języku Java nie ma składni pozwalającej na
  bezpośrednie tworzenie dynamicznych wywołao 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ę pojawid 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 poradzid 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ą             50
Generowanie instrukcji INVOKEDYNAMIC

• W sieci jest kilka przykładów jak generowad 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




                                                                                      51
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)




                                                    52
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śniad…




                                                                                 53
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") );
     }
}




                                                                                                                           54
DemoIndySecond (krok I)         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.

public class HelloInDyWorld1 {                             Implementacja InvokeDynamic.prepare jest wyjaśniona na późniejszych slajdach.
     public static String myConfituraMethod(String s)    { return s + " 2012"; }
                                                       „run me” to odpowiednik nazwy wywoływanej metody (czyli tak jakby wywoład
                                                       X.runme). A przynajmniej tak jak my, jako wywołujący (caller), to „widzimy”.
     public static CallSite myBSM(MethodHandles.Lookup Warto zauważyd, że nie obowiązują nas ograniczenia co do identyfikatorów (tu
                                                        caller, String methodName, MethodType methodType, Object... bsmArgs)
     {                                                 jest spacja w nazwie wywoływanej metody ).
             MethodHandle mh = null;
                                                        Drugi argument, to typ wywoływanej metody (znowu: z punktu widzenia
             try {                                      wywołującego).
                 mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod",
                                                        Kolejne argumenty dotyczą BSM (bootstrap method), tj. jej nazwy, klasy w której
                                                         MethodType.methodType(String.class, String.class));
                                                        ona się znajduje, jej typu oraz jej (opcjonalnych) parametrów. Metoda BSM:
             } catch (NoSuchMethodException | IllegalAccessException wykonywana co najwyżej jeden raz
                                                        • jest zawsze
                                                                       e) { e.printStackTrace(); }
                                                           • i tylko przy pierwszym napotkaniu przez JVM danej instrukcji
             return new ConstantCallSite(mh);                INVOKEDYNAMIC (czyli very lazy, fakt, który jeszcze wykorzystamy…)
     }

     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") );
     }
}




                                                                                                                                  55
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);
     }     Bootstrap method.

     publicJej zadaniemmain(String args[]) throws Throwable {
            static void jest skonstruowad dane miejsce wywoływania metody, czyli określid za pomocą uchwytu do metody (MethodHandle)
           docelową metodę (target), która będzie wywoływana kiedy JVM napotka daną instrukcję INVOKEDYNAMIC.
              MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class),
           Powtarzam się, ale: BSM jest wywoływana co najwyżej raz, przy pierwszym napotkaniu tę instrukcji INVOKEDYNAMIC.
           Po wykonaniu się "myBSM", HelloInDyWorld1.class, obecne i każde następne wykonanie tej instrukcji INVOKEDYNAMIC
                             BSM (tj. ustaleniu docelowej metody),
           spowoduje wykonanie ustalonej przez BSM docelowej metody.Lookup.class, String.class, MethodType.class, Object[].class));
                             MethodType.methodType(CallSite.class,
           BSM zwraca obiekt opakowujący uchwyt do docelowej metody, czyli CallSite.
              System.out.println( mh.invoke("Confitura") );
           Możemy też przekazad naszą intencję, czy chcemy w przyszłości zmieniad 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 przechowywad w swoim kodzie. Można także tworzyd własne klasy specjalizowane CallSite.
           Typ uchwytu do metody zwracanego przez BSM musi byd 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 przekazad dodatkowe parametry (bsmArgs). W sumie to także nazwa
           wywoływanej metody („run me”) jest takim dodatkowym parametrem, bo jedyne co musi byd zachowane, to typ uchwytu
           zwracany z BSM - musi byd zgodny z tym, co określił caller w callsite.                                                56
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));
                                                              Docelowa metoda (target).
              } catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }
                                                                Jej sygnatura (=typ, MethodType) NIE musi byd w 100% zgodna z tym, co
             return new ConstantCallSite(mh);                   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 wymagao (typu) określonego w miejscu
                                                                wywoływania metody.
     public static void main(String args[]) throws Throwable {
              MethodHandle mh = InvokeDynamic.prepare("runTo pewnie oczywiste, ale to co przychodzi jako argument tej metody (tu:
                                                                me", MethodType.methodType(String.class, String.class),
                            "myBSM", HelloInDyWorld1.class,    String s), to są argumenty przekazane w wywołaniu metody (tu:
                                                               „Confitura”). No chyba że, ciąg kombinatorów to zmodyfikował (, będzie
                            MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class));
                                                               później przykład pięknie to pokazujący).
              System.out.println( mh.invoke("Confitura") );
     }
}




                                                                                                                              57
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);
                                                                          Dynamiczne wywołanie metody.
     }

     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") );
     }
}




                                                                                                                           58
InvokeDynamic.prepare
• własny kod pomocniczy (z braku składni w Java dla InvokeDynamic)
• pozwala utworzyd 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

                                                                   59
Klasa InvokeDynamic
• jej zrozumienie nie jest szczególnie trudne, ale trzeba mied
  elementarną wiedzę o ASM
   – lub rozumied 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ład jej metodę



                                                                 60
HelloIndyWorld2

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




                                                  61
DemoIndySecond.HelloIndyWorld2
public 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));
                       else
                           mh = 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") );
     }
}
                                                                                                                                                            62
HelloIndyWorld3
• przykład pokazuje jak uczynid 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…



                                                                           63
DemoIndySecond.HelloIndyWorld3
public 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 !") );
       }
}                                                                                                                                                        64
DEMO III
czyli krótki benchmark wywołao metod:
 InvokeDynamic vs. Static vs. Reflection




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

 • bo dosyd często podnoszą się głosy, że taki
   dynamiczny mechanizm wywoływania metod
   musi byd wolny
   – „jak pokazuje doświadczenie z refleksją…”
 • niekoniecznie.
   – po to przede wszystkim stworzono InvokeDynamic
     (JSR-292), aby pogodzid dynamizm z wydajnością
      • a przynajmniej dad JIT na to szansę
         – z każdym kolejnym uaktualnieniem Java 7 spodziewałbym się
           też coraz większej wydajności InDy
                                                                       66
DemoIndyThird


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




                                                                            67
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.   statyczny
   3.   refleksyjny
   4.   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);
                                                                              68
        }
Wyniki ?




           69
Benchmark INVOKE DYNAMIC
  1999800000000, TIME: 228 ms
  1999800000000, TIME: 150 ms
  1999800000000, TIME: 127 ms
  1999800000000, TIME: 132 ms
  1999800000000, TIME: 133 ms
  1999800000000, TIME: 140 ms
  1999800000000, TIME: 144 ms

  Benchmark NORMAL (STATIC)
  1999800000000, TIME: 142 ms
  1999800000000, TIME: 154 ms
  1999800000000, TIME: 122 ms
  1999800000000, TIME: 135 ms



                                 
  1999800000000, TIME: 139 ms
  1999800000000, TIME: 131 ms
  1999800000000, TIME: 126 ms

    Benchmark REFLECTIVE
 1999800000000, TIME: 4513 ms
 1999800000000, TIME: 4258 ms
 1999800000000, TIME: 4248 ms
 1999800000000, TIME: 4290 ms
 1999800000000, TIME: 4236 ms
 1999800000000, TIME: 4156 ms
 1999800000000, TIME: 4195 ms

Benchmark REFLECTIVE NO BOXING
 1999800000000, TIME: 3077 ms
 1999800000000, TIME: 2879 ms
 1999800000000, TIME: 2921 ms
 1999800000000, TIME: 3011 ms
 1999800000000, TIME: 2910 ms
 1999800000000, TIME: 3010 ms
 1999800000000, TIME: 2845 ms        70
DEMO IV
czyli przykład wykorzystania jednej z
              cech BSM




                                        71
Ale po co ?

• pewnie wciąż zadajecie sobie pytanie, po co
  ten cały InvokeDynamic ?
• odpowiedź wciąż brzmi:
  – aby pisad bardziej dynamiczny kod 
• w tym przykładzie będzie pokazane
  wykorzystanie jednej z ważnych cech
  InvokeDynamic (i mojej również)
  – laziness
                                                72
BSM
• jak pamiętacie, BSM dla instrukcji
  INVOKEDYNAMIC jest wykonywana dopiero
  wtedy, gdy JVM taką instrukcję napotka
• czyli można ten fakt wykorzystad do tego, aby
  bardzo późno wykonywad pewne operacje (np.
  inicjalizacje)
  – „późno” = „dopiero gdy jest to potrzebne/używane”



                                                  73
Przykład – Lazy Constant
• załóżmy, że chcecie mied w swojej klasie stałą
  wartośd (np. XML), ale inicjowaną poprzez
  wykonanie jakiejś bardziej złożonej logiki
  – załóżmy, że ta logika potencjalnie może
    wykonywad się długo
     • a w Waszej aplikacji czas jest istotny




                                                   74
Pomysł 1
    Stała na poziomie klasy, inicjowana w bloku statycznym
public class ConstantResourceImpl implements ConstantResource {
              public static final String xml;
              static {
                            xml = ParseHelper.doSomeHeavyParsing("test.xml");
              }

            @Override
            public void notNeedingTheXMLHere() {
                         //Hey, I am NOT using the 'xml' constant here !
            }

            @Override
            public void badlyNeedingTheXMLHere() {
                         //I will be using the 'xml' constant here !
                         try {
                                        System.out.println(xml);
                         } catch (Throwable e) { e.printStackTrace(); }
            }
}
                                                                                75
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();
              }
}




                                                                                        76
Pomysł 2
      (bardziej lazy, a w zasadzie to very lazy)
• „dostęp do wartości stałej” = „wykonanie metody zwracającej (stałą)
  wartośd”
   – 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śd 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 byd określone dynamicznie (= w czasie wykonania)
   – dodatkowo: podejście z InDy jest bardziej bezpieczne wątkowo ! Patrz
                                                                         77
      gwarancje określone w JVM spec.
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(); }
     }

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

     @Override
     public void badlyNeedingTheXMLHere() { //I will be using the 'xml' constant here !
            try { System.out.println( mh.invoke()); } catch (Throwable e) { e.printStackTrace(); }
     }                                                                                                       78
}
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ę.




                                                                                                          79
A gdy użyjemy stałej…

public class LazyConstantsMainWithIndy {

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




                                                                                                        80
Ten sam trick może byd użyteczny w
   wielu Waszych programach…




                                     81
Github
• DemoIndyFourth
https://github.com/waldekkot/Confitura2012_InvokeDynamic/tree/master/DemoIndyFourth




                                                                               82
DEMO V
czyli przyspiesz swój kod rekurencyjny
(a tak naprawdę, to: jak czerpad intelektualną przyjemnośd z
         czytania kodu zawierającego InvokeDynamic)
       (zwłaszcza, gdy tego kodu nie musisz napisad )




                                                               83
Czy masz pomysł jak przyspieszyd 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));
     }
}

                                                                                                        84
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…




                                                       85
Memoization
• wykorzystamy dynamiczne wywoływanie metod, aby ten
  piękny i przejrzysty kod przyspieszyd
   – 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

                                                                     86
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 


                                                                         87
Ale zanim nastąpią wyjaśnienia,
             zobaczcie efekt koocowy !




SpeedRecurenceWithIndy.java

                                            88
Zresztą, w zasadzie to nawet
zmieniliśmy złożonośd obliczeniową tej
               metody!
  [z wykładniczej na niemal stałą ;-)]




                Dla N > 102 kooczy się pojemnośd long (263 -1) …

                Zamieniając long na BigInteger można spokojnie
                podad i N = 3000 (ponad 600 cyfrowa liczba). Czas:
                                                              89
                ~50 ms . Ten przykład też jest na GitHub-ie.
OK, to na czym polegają różnice ?




                                    90
1. Zamiast wywołao fib(n) użycie InvokeDynamic
public 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));
        }                                                                                                        91
}
2. Bufor do przechowywania wyników
private static ClassValue<HashMap<String, HashMap<Object, Object>>> cacheTables = new ClassValue<HashMap<String,
                                      HashMap<Object, Object>>>() {
              @Override
              protected 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śd 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)              Notabene: szkoda, że dla klas anonimowych nie można
               • wynik metody (wartośd)            użyd nowego w Java 7 operatora diamond. Fajniej
                                                             byłoby napisad: „= new ClassValue<>()”, a kompilator
                                                                                                             92
                                                             się domyślił reszty…
3. Metody pomocnicze dla BSM (i ich uchwyty)
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)
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); } 93
}
4. Bootstrap method (I)
Aaaaaaby zamienid statyczne wywołania * fib() + na dynamiczne, wystarczyłoby tyle:

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);
}




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




                                                                                                                94
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());
                                                                       WTF !?!
              identity = identity.asType(identity.type().changeParameterType(0, Object.class));
              identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));
                                                                       Spokojnie, będę po kolei całośd wyjaśniad !

              MethodHandle update = UPDATE.bindTo(cache);           Poznając InvokeDynamic warto poznad jeden trick :
              update = update.asType(type.insertParameterTypes(0, type.returnType()));
                                                                    TRZEBA CZYTAD OD KOŃCA (czyli od dołu w górę) !!!
              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);
                                                                                                                        95
              return new ConstantCallSite(memoize);
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);
              }                                                         Dla przypomnienia: zadaniem BSM jest utworzyd i skonfigurowad
                                                                        miejsce wywołania (callsite). Skonfigurowad, czyli określid docelową
              MethodHandle identity = MethodHandles.identity(type.returnType()); target.
                                                                        metodę –
              identity = identity.asType(identity.type().changeParameterType(0, Object.class));
                                                                         Tutaj, utworzone miejsce wywołania, tj. jego target, mimo, iż będzie
              identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));
                                                                         to całkiem skomplikowany łaocuch operacji, nie będzie się zmieniał,
                                                                         więc jest typu ConstantCallSite.
              MethodHandle update = UPDATE.bindTo(cache);
              update = update.asType(type.insertParameterTypes(0, type.returnType()));
                                                                         A to sprzyja optymalizacjom wykonywanym przez JIT.

                                                                       Łaocuch kombinatorów musi zapewnid, że typ uchwytu zwracanego
              MethodHandle fallback = MethodHandles.foldArguments(update, target);
              fallback = MethodHandles.dropArguments(fallback, 0, Object.class); memoize) będzie zgodny z typem miejsca wywołania
                                                                       z BSM (tu:
                                                                       (czyli w tym przykładzie: (long)long, bo taki jest typ metody fib).
                                                                       Ale ten BSM jest ładniejszy (bardziej generyczny) – nie ma
              MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback); operacje w rodzaju
                                                                       „zaszytych” tych typów – stąd
                                                                       type.parameterType(0), type.returnType(), itp.
              MethodHandle cacheQuerier = MAP_GET.bindTo(cache);
              cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));

              MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);
                                                                                                                                       96
              return new ConstantCallSite(memoize);
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);

                                                               Argumentami foldArguments są uchwyty do metod. foldArguments działa w ten
              String selector = name + type.toMethodDescriptorString();
                                                               sposób, że:
              HashMap<Object, Object> cache = cacheTable.get(selector);
                                                               1. najpierw wywołuje uchwyt podany jako drugi argument (tu: cacheQuerier)
              if (cache == null) {
                                                               2. jeśli zwróci on wynik (także null, ale nie void), to ten wynik jest wstawiany
                               cache = new HashMap<Object, Object>(); !) do listy argumentów na pozycji 0 pierwszego uchwytu (tu:
                                                                  (INSERT
                               cacheTable.put(selector, cache); combiner)
              }                                                3. wywoływany jest pierwszy uchwyt (tu: combiner)

                                                               Tutaj, cacheQuerier będzie najpierw odpytywał bufor (za pomocą metody
              MethodHandle identity = MethodHandles.identity(type.returnType());
              identity = identity.asType(identity.type().changeParameterType(0, Object.class));tego odpytywania (obiekt/wynik obliczeo lub
                                                               HashMap.get) i wstawiał wynik
                                                               null).
              identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));
                                                          Typ wynikowego uchwytu z foldArguments (tu: memoize) musi byd zgodny z
              MethodHandle update = UPDATE.bindTo(cache); typem miejsca wywołania *tu: (long)long, bo taki jest typ metody fib+
              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);
                                                                                                                                         97
              return new ConstantCallSite(memoize);
4. Bootstrap method (II)
public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws
                 ReflectiveOperationException {                     Zadaniem cacheQuerier jest odpytanie bufora (chod nie jestem przekonany
                                                                    czy ‚cache’ to jest tutaj właściwa nazwa), czy dla danego argumentu (wartości
                 MethodHandle target = lookup.findStatic(staticType, name, type);
                                                                    przekazanej do metody fib), który tutaj jest kluczem, została wcześniej
                                                                    przypisana wartośd (rezultat wcześniejszego wywołania).
                 HashMap<String, HashMap<Object, Object>> cacheTable nie, to zwracany jest null.
                                                                    Jeśli = cacheTables.get(staticType);
                                                                    Jeśli tak, to zwracany jest obiekt (=wstawiony wcześniej, wynik wykonania
                                                                    metody).
                 String selector = name + type.toMethodDescriptorString();
                 HashMap<Object, Object> cache = cacheTable.get(selector);
                 if (cache == null) {                               Wracając trochę do poprzedniego slajdu i metody foldArguments:
                                                                    Uchwyt cacheQuerier w czasie wykonania (!) zawsze zwróci wynik różny od
                                  cache = new HashMap<Object, Object>();
                                                                    void (bo tak działa HashMap.get), więc jeśli chodzi o typowanie, to pierwszy
                                  cacheTable.put(selector, cache);  argument uchwytu combiner będzie pominięty (tak działa foldArguments).
                 }                                                  Czyli (tu) typ uchwytu combiner musi byd (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 mied memoize).
               MethodHandle identity = MethodHandles.identity(type.returnType());
               identity = identity.asType(identity.type().changeParameterType(0, cacheQuerier musi mied typ (Object)long *bo taki jest wymóg
                                                                    Kontynuując: Object.class));
                                                                    foldArguments). cacheQuerier to uchwyt do metody HashMap.get, która ma
               identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));
                                                                    typ (Object)Object. A zatem, za pomocą metody asType, adaptujemy uchwyt
                                                                    cacheQuerier, tak, aby spełnid wymóg foldArguments i „zaprezentowad”
               MethodHandle update = UPDATE.bindTo(cache); cacheQuerier, jako uchwyt o typie: (Object)long.
               update = update.asType(type.insertParameterTypes(0, type.returnType()));
                                                                    Gra i buczy…
               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);
                                                                                                                                        98
               return new ConstantCallSite(memoize);
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

Contenu connexe

Similaire à INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

Nie tylko C# - Ekosystem Microsoft dla programistów
Nie tylko C# - Ekosystem Microsoft dla programistówNie tylko C# - Ekosystem Microsoft dla programistów
Nie tylko C# - Ekosystem Microsoft dla programistówintive
 
Podstawy programowania w Drupalu - Drupal idzie na studia - Jarosław Sobiecki
Podstawy programowania w Drupalu - Drupal idzie na studia - Jarosław SobieckiPodstawy programowania w Drupalu - Drupal idzie na studia - Jarosław Sobiecki
Podstawy programowania w Drupalu - Drupal idzie na studia - Jarosław SobieckiGrzegorz Bartman
 
Codeception - jak zacząć pisać automatyczne testy do Drupala [PL]
Codeception - jak zacząć pisać automatyczne testy do Drupala [PL]Codeception - jak zacząć pisać automatyczne testy do Drupala [PL]
Codeception - jak zacząć pisać automatyczne testy do Drupala [PL]Droptica
 
Pułapki programowania obiektowego
Pułapki programowania obiektowego Pułapki programowania obiektowego
Pułapki programowania obiektowego Adam Sawicki
 
Czego oczekiwać od modułów w Javie 9
Czego oczekiwać od modułów w Javie 9Czego oczekiwać od modułów w Javie 9
Czego oczekiwać od modułów w Javie 9Piotr Kubowicz
 
Delphi 2006. Ćwiczenia praktyczne
Delphi 2006. Ćwiczenia praktyczneDelphi 2006. Ćwiczenia praktyczne
Delphi 2006. Ćwiczenia praktyczneWydawnictwo Helion
 
Produkcja aplikacji internetowych
Produkcja aplikacji internetowychProdukcja aplikacji internetowych
Produkcja aplikacji internetowychTomasz Borowski
 
Jak stworzyć udany system informatyczny
Jak stworzyć udany system informatycznyJak stworzyć udany system informatyczny
Jak stworzyć udany system informatycznyqbeuek
 
CDI Portable Extensions
CDI Portable ExtensionsCDI Portable Extensions
CDI Portable ExtensionsAdam Warski
 
Programowanie na wiele platform mobilnych - 2012
Programowanie na wiele platform mobilnych - 2012Programowanie na wiele platform mobilnych - 2012
Programowanie na wiele platform mobilnych - 2012Cezary Walenciuk
 
Delphi 2005. 303 gotowe rozwiązania
Delphi 2005. 303 gotowe rozwiązaniaDelphi 2005. 303 gotowe rozwiązania
Delphi 2005. 303 gotowe rozwiązaniaWydawnictwo Helion
 
CI oraz CD w złożonym projekcie o małym budżecie
CI oraz CD w złożonym projekcie o małym budżecieCI oraz CD w złożonym projekcie o małym budżecie
CI oraz CD w złożonym projekcie o małym budżecieGrzegorz Godlewski
 
Tworzenie i utrzymywanie czystego kodu
Tworzenie i utrzymywanie czystego koduTworzenie i utrzymywanie czystego kodu
Tworzenie i utrzymywanie czystego kodupabloware
 

Similaire à INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012) (20)

Nie tylko C# - Ekosystem Microsoft dla programistów
Nie tylko C# - Ekosystem Microsoft dla programistówNie tylko C# - Ekosystem Microsoft dla programistów
Nie tylko C# - Ekosystem Microsoft dla programistów
 
Podstawy programowania w Drupalu - Drupal idzie na studia - Jarosław Sobiecki
Podstawy programowania w Drupalu - Drupal idzie na studia - Jarosław SobieckiPodstawy programowania w Drupalu - Drupal idzie na studia - Jarosław Sobiecki
Podstawy programowania w Drupalu - Drupal idzie na studia - Jarosław Sobiecki
 
Codeception - jak zacząć pisać automatyczne testy do Drupala [PL]
Codeception - jak zacząć pisać automatyczne testy do Drupala [PL]Codeception - jak zacząć pisać automatyczne testy do Drupala [PL]
Codeception - jak zacząć pisać automatyczne testy do Drupala [PL]
 
Wprowadzenie do PHPUnit
Wprowadzenie do PHPUnitWprowadzenie do PHPUnit
Wprowadzenie do PHPUnit
 
Pułapki programowania obiektowego
Pułapki programowania obiektowego Pułapki programowania obiektowego
Pułapki programowania obiektowego
 
Czego oczekiwać od modułów w Javie 9
Czego oczekiwać od modułów w Javie 9Czego oczekiwać od modułów w Javie 9
Czego oczekiwać od modułów w Javie 9
 
Delphi 2006. Ćwiczenia praktyczne
Delphi 2006. Ćwiczenia praktyczneDelphi 2006. Ćwiczenia praktyczne
Delphi 2006. Ćwiczenia praktyczne
 
JavaScript, Moduły
JavaScript, ModułyJavaScript, Moduły
JavaScript, Moduły
 
university day 1
university day 1university day 1
university day 1
 
Produkcja aplikacji internetowych
Produkcja aplikacji internetowychProdukcja aplikacji internetowych
Produkcja aplikacji internetowych
 
Refaktoryzacja
RefaktoryzacjaRefaktoryzacja
Refaktoryzacja
 
Jak stworzyć udany system informatyczny
Jak stworzyć udany system informatycznyJak stworzyć udany system informatyczny
Jak stworzyć udany system informatyczny
 
Praktyczny kurs Delphi
Praktyczny kurs DelphiPraktyczny kurs Delphi
Praktyczny kurs Delphi
 
CDI Portable Extensions
CDI Portable ExtensionsCDI Portable Extensions
CDI Portable Extensions
 
Programowanie na wiele platform mobilnych - 2012
Programowanie na wiele platform mobilnych - 2012Programowanie na wiele platform mobilnych - 2012
Programowanie na wiele platform mobilnych - 2012
 
Delphi 2005. 303 gotowe rozwiązania
Delphi 2005. 303 gotowe rozwiązaniaDelphi 2005. 303 gotowe rozwiązania
Delphi 2005. 303 gotowe rozwiązania
 
CI oraz CD w złożonym projekcie o małym budżecie
CI oraz CD w złożonym projekcie o małym budżecieCI oraz CD w złożonym projekcie o małym budżecie
CI oraz CD w złożonym projekcie o małym budżecie
 
Praktyczny kurs Java
Praktyczny kurs JavaPraktyczny kurs Java
Praktyczny kurs Java
 
Tworzenie i utrzymywanie czystego kodu
Tworzenie i utrzymywanie czystego koduTworzenie i utrzymywanie czystego kodu
Tworzenie i utrzymywanie czystego kodu
 
Java. Kompendium programisty
Java. Kompendium programistyJava. Kompendium programisty
Java. Kompendium programisty
 

INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

  • 1. INVOKEDYNAMIC = bardziej dynamiczna JVM Confitura 2012 Waldek Kot waldek.kot@gmail.com wersja 1.1 1
  • 2. Prezentacja mocno korzysta z animacji, więc warto się przełączyd na tryb „Slide Show” 2
  • 3. Prolog Niniejsza prezentacja zawiera materiał, który chciałem zaprezentowad 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 zobaczyd co dzieje się na ekranie (tj. rzędom od 4-go w górę na pewno nie) … a przynajmniej tak to sobie tłumaczę  3
  • 4. Agenda 1. Motywacja – dlaczego taki temat ? 2. Trochę teorii o InvokeDynamic 3. Praktyka – zaczynając od „Hello World”  4
  • 5. Motywacja czyli dlaczego zgłosiłem taki temat na Confiturę 2012 ? 5
  • 6. Motywacja pozytywna InvokeDynamic (krócej: InDy) to największa nowośd w Java 7 6
  • 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 powiedzied, że refleksja w Javie jest użyteczna tylko dla wybraocó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”  7
  • 8. Dlaczego warto poznad 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 zrozumied 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ę wspomnied 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ówid wyłącznie o wykorzystaniu InDy w języku Java. 8
  • 9. Po co InvokeDynamic ? Żeby łatwiej tworzyd 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 9
  • 10. Po co InvokeDynamic ? • … ale: wszystko co daje InDy można dzisiaj zrobid innymi metodami („zasymulowad”) • prawda, tyle tylko, że dzięki InDy: – będzie prościej, bo… nie musimy tego robid • „Najlepszy kod to taki którego nie trzeba pisad” – będzie wydajniej, bo: • kod z InDy jest lepiej „rozumiany” przez kompilator JIT JVM => JIT będzie w stanie wykonad o wiele więcej optymalizacji, niż przy zastosowaniu „symulatorów” 10
  • 11. Ważna uwaga • większośd przykładów i kodu, który tutaj pokazuję ukradłem  – adaptacja filozofii: „najlepszy kod, to taki, którego nie trzeba pisad” • mój mały wkład tutaj polega na tym, aby: 1. „odczarowad” temat InvokeDynamic 2. poprzez przykłady - czasem trywialne - zachęcid do samodzielnego poznawania InDy 3. ułatwid czytanie ze zrozumieniem kodu wykorzystującego InDy, dostępnego w sieci • i czerpad z tego taką przyjemnośd, jaką ja miałem (i mam nadal) • chodby 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/ 11
  • 13. InvokeDynamic Pozwala programiście mied kontrolę nad tak czymś tak elementarnym, jak wywoływanie metod 13
  • 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ą . 14
  • 15. MethodHandle - Hello World • do zabawy z większością tego co oferuje Indy wystarczy Java 7 i IDE • czyli tyle wystarczy rozumied, aby zacząd : public class HelloIndyWorld { public static void main(String[] args) { } } 15
  • 16. MethodHandle – Hello World Do tworzenia uchwytów do metod używamy m.in. metod (statycznych) klasy package pl.confitura.invokedynamic; MethodHandles. Śmiesznie, ale w tym przykładzie tworzymy uchwyt do nieistniejącej metody ! import java.lang.invoke.MethodHandle; constant zawsze zwraca stałą wartośd, tu: import java.lang.invoke.MethodHandles; typu String, równą „Hello World !”. Metoda invoke wywołuje metodę na którą wskazuje uchwyt. public class HelloIndyWorld { public static void main(String[] args) throws Throwable { MethodHandle mh = MethodHandles.constant(String.class, "Hello World !"); System.out.println(mh.invoke()); } } 16
  • 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 18 referencyjnych.
  • 19. MethodHandle - kombinatory • API udostępnione w ramach InvokeDynamic pozwala na całkiem złożone „manipulacje” uchwytami do metod – możemy dostawad się do różnego rodzaju metod (statycznych, wirtualnych, konstruktorów, itp.), a także w szerokim zakresie manipulowad 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 (łaocuch) 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 aplikowad swoje optymalizacje np. method inlining 19 – JIT może taki graf „przejśd”, węzeł po węźle i „zrozumied” go
  • 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 byd silnie typizowalna (strong typing) – a zatem te z optymalizacji JIT, które korzystają z informacji o typach, mogą byd 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 poznad, 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 znad dopiero w czasie wykonania (kompilator daje tu niewiele) 2. JVM jest absolutnie bezwzględny jeśli chodzi o zgodnośd typów ! • bezpieczeostwo i szybkośd 20
  • 21. Nowy bytecode: INVOKEDYNAMIC Pierwsze w historii rozszerzenie listy instrukcji JVM ! • chod można spekulowad, ż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śd wpływu programisty na sposób wywoływania metod 21
  • 22. Bytecode: INVOKEDYNAMIC • przy pierwszym napotkaniu tej instrukcji, JVM wywołuje metodę (tzw. BSM – bootstrap method), której zadaniem jest określid 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) 22
  • 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 zmodyfikowad to „miejsce”, tj. podstawid tam inny uchwyt – dynamizm InDy w akcji ! 23
  • 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 aplikowad te ze swoich optymalizacji, które zakładają, że zawsze będzie wywoływana ta sama metoda (czyli należy oczekiwad, że szybkośd 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 tworzyd swoje własne specjalizacje klas CallSite ! – z własną logiką, stanem, itp. 24
  • 25. Inne przydatne konstrukcje API InvokeDynamic udostępnia jeszcze kilka innych przydatnych konstrukcji pomocniczych (prostszych i/lub bardziej wydajnych niż gdyby samodzielnie budowad ich odpowiedniki): • java.lang.ClassValue<T> – pozwala programiście przypisad 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żyd • SwitchPoint – rodzaj semafora, który bezpiecznie wątkowo może poinformowad o pewnej zmianie związane z nim uchwyty – będzie później przykład praktyczny, który to lepiej objaśni • MethodHandleProxies – pozwala utworzyd 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)  25
  • 26. Czas na DEMO ! (wiem, nareszcie ) 26
  • 27. DEMO I czyli zabawy z MethodHandle 27
  • 28. Co potrzeba ? • Java 7 – im wyższa wersja, tym lepszej wydajności InvokeDynamic należy się spodziewad • IDE – tworzymy zwykły projekt Java 28
  • 29. MethodHandle – Hello World (było) Do tworzenia uchwytów do metod używamy m.in. metod (statycznych) klasy MethodHandles. package pl.confitura.invokedynamic; Śmiesznie, ale w tym przykładzie tworzymy uchwyt do nieistniejącej metody ! Constant zawsze zwraca stałą wartośd, tu: typu import java.lang.invoke.MethodHandle; String, równą „Hello World !”. import java.lang.invoke.MethodHandles; Metoda invoke wywołuje metodę na którą wskazuje uchwyt. public class HelloIndyWorld { public static void main(String[] args) throws Throwable { MethodHandle mh = MethodHandles.constant(String.class, "Hello World !"); System.out.println(mh.invoke()); } } 29
  • 30. MethodHandle – identity Uchwyt do metody uzyskany poprzez identity, gdy jest wywołany, zwraca swój argument (podanego typu, tu: String) 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 !")); } } To jest argument do uchwytu do metody (tu: mh). Te argumenty będą przekazane do metody na którą wskazuje uchwyt (tu: identity). 30
  • 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ślid typ metody, czyli jakiego } typu są parametry i wynik podanej metody. Więcej informacji: klasa MethodType w java.lang.invoke 31
  • 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 • invokeWithArguments Invoke dokonuje opcjonalnie konwersji argumentów i wyniku do tego co oczekuje wywołujący (caller). InvokeExact jest szybszy, ale wymagana jest 100% zgodnośd 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. 32
  • 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 … 33
  • 34. MethodHandle – coś ciekawszego: uchwyt do własnej metody public class HelloIndyWorld { Lookup pozwala uzyskad uchwyt do public static class MyClass { różnorodnych metod, np. tutaj do metody statycznej myConcat w klasie MyClass. static public String myConcat(String s1, String s2) { return s1 + s2; W 100% muszą zgadzad się nie tylko nazwa } metody, ale także jej sygnatura (określona przez typ metody – MethodType [mt]) } 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")); } } 34
  • 35. MethodHandle – uchwyt do metody wirtualnej public class HelloIndyWorld { public static class MyClass { private String s; Metody wirtualne klasy wyszukujemy za pomocą public MyClass(String s) { findVirtual. this.s = s; W metodach wirtualnych, implicite, ich pierwszy } argument określa obiekt na którym ta metoda będzie wykonana (receiver). public int howManyCharacters(String s) { Za pomocą bindTo możemy utworzyd uchwyt do return (s + this.s).length(); metody, w którym ten argument (receiver) będzie } na stałe ustawiony na wybrany obiekt. } 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")); } } 35
  • 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… 36
  • 37. MethodHandle – składanie uchwytów (kombinatory) public class HelloIndyWorld { public static class MyClass { private String s; Przykład utworzenia nowego uchwytu do metody – za pomocą metody public MyClass(String s) { filterArgument. Najpierw na podanym this.s = s; argumencie (0, czyli „ma kota”), wykona się } metoda wskazywana przez uchwyt mhToUpper (czyli metoda wirtualna public String myVirtualConcat(String s) { „toUpperCase” dla obiektu klasy String). return this.s + s; Zwrócony z niej wynik (czyli „MA KOTA”) } będzie nowym argumentem dla mh } (pierwszy argument w filterArguments). 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")); } } 37
  • 38. MethodHandle – filtrowanie wyników metod public class HelloIndyWorld { public static class MyClass { private String s; public MyClass(String s) { Tu najpierw wykonywana jest metoda na this.s = s; którą wskazuje mh (czyli myVirtualConcat), } a następnie na jej wyniku (czyli „Ala ma kota”) wykonywana jest metoda na którą public String myVirtualConcat(String s) { wskazuje mhToUpper. 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")); } } 38
  • 39. MethodHandle – interceptor typu ‚before’ public class HelloIndyWorld { public static class MyClass { private String s; public MyClass(String s) { foldArguments działa w ten sposób, że najpierw this.s = s; wykonywany jest drugi argument (mhInterceptor), a } jego wynik (o ile nie jest void) jest WSTAWIANY (INSERT) jako argument do wywołania pierwszego public String myVirtualConcat(String s) { argumentu (mh).Po czym wywoływany jest mh. return this.s + s; Czyli poniższy kod nie zadziała. Dlaczego ? Bo po } wstawieniu dodatkowego argumentu (czyli wyniku z mhInterceptor) nie zgadzają się typy uchwytów w public static String myInterceptor(String s) { foldArguments (a muszą one byd takie same). System.out.println("Intercepted, with arg s: " + s); Komunikat błędu jest przy tym dosyd mylący … 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")); } } 39
  • 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) { Za pomocą metody dropArguments trzeba pozbyd się return this.s + s; niepotrzebnego już argumentu (czyli „ma kota”). } Pozycja argumentów liczona jest od 0, ale ponieważ wynik wywołania mhInterceptor jest WSTAWIANY na public static String myInterceptor(String s) { pozycję 0, to oryginalne argumenty przesuwają się (czyli System.out.println("Intercepted, with arg s: " + s); „ma kota” jest teraz na pozycji 1). 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")); } } 40
  • 41. MethodHandle – interceptor typu ‚before’ (najpoprawniej ) public class HelloIndyWorld { public static class MyClass { private String s; public MyClass(String s) { this.s = s; } Bo tak jest bardziej generycznie. Typ usuwanego argumentu jest zgodny z typem parametru metody public String myVirtualConcat(String s) { myVirtualConcat). Dla wyjaśnienia: return this.s + s; • typ mh to: (String)String } • mh.type().parameterType(0) odnosi się do pierwszego (i tu jedynego) argumentu metody public static String myInterceptor(String s) { myVirtualConcat, sprzed wstawienia wyniku z System.out.println("Intercepted, with arg s: " + s); interceptora 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")); } } 41
  • 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")); } } 42
  • 43. MethodHandles / MethodHandle Dostępne są także metody: • wstawiające argumenty wybranego typu (insertArgument) • zmieniające kolejnośd 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) • … 43
  • 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 44
  • 45. MethodHandle – przykład z .guardWithTest public 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")); } } 45
  • 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")); } } 46
  • 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 47
  • 48. MethodHandleProxies - przykład public 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") ); } } 48
  • 49. DEMO II czyli wywołanie instrukcji bytecode’u INVOKEDYNAMIC 49
  • 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łao 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ę pojawid 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 poradzid 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ą 50
  • 51. Generowanie instrukcji INVOKEDYNAMIC • W sieci jest kilka przykładów jak generowad 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 51
  • 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) 52
  • 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śniad… 53
  • 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") ); } } 54
  • 55. DemoIndySecond (krok I) 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. public class HelloInDyWorld1 { Implementacja InvokeDynamic.prepare jest wyjaśniona na późniejszych slajdach. public static String myConfituraMethod(String s) { return s + " 2012"; } „run me” to odpowiednik nazwy wywoływanej metody (czyli tak jakby wywoład X.runme). A przynajmniej tak jak my, jako wywołujący (caller), to „widzimy”. public static CallSite myBSM(MethodHandles.Lookup Warto zauważyd, że nie obowiązują nas ograniczenia co do identyfikatorów (tu caller, String methodName, MethodType methodType, Object... bsmArgs) { jest spacja w nazwie wywoływanej metody ). MethodHandle mh = null; Drugi argument, to typ wywoływanej metody (znowu: z punktu widzenia try { wywołującego). mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod", Kolejne argumenty dotyczą BSM (bootstrap method), tj. jej nazwy, klasy w której MethodType.methodType(String.class, String.class)); ona się znajduje, jej typu oraz jej (opcjonalnych) parametrów. Metoda BSM: } catch (NoSuchMethodException | IllegalAccessException wykonywana co najwyżej jeden raz • jest zawsze e) { e.printStackTrace(); } • i tylko przy pierwszym napotkaniu przez JVM danej instrukcji return new ConstantCallSite(mh); INVOKEDYNAMIC (czyli very lazy, fakt, który jeszcze wykorzystamy…) } 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") ); } } 55
  • 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); } Bootstrap method. publicJej zadaniemmain(String args[]) throws Throwable { static void jest skonstruowad dane miejsce wywoływania metody, czyli określid za pomocą uchwytu do metody (MethodHandle) docelową metodę (target), która będzie wywoływana kiedy JVM napotka daną instrukcję INVOKEDYNAMIC. MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class), Powtarzam się, ale: BSM jest wywoływana co najwyżej raz, przy pierwszym napotkaniu tę instrukcji INVOKEDYNAMIC. Po wykonaniu się "myBSM", HelloInDyWorld1.class, obecne i każde następne wykonanie tej instrukcji INVOKEDYNAMIC BSM (tj. ustaleniu docelowej metody), spowoduje wykonanie ustalonej przez BSM docelowej metody.Lookup.class, String.class, MethodType.class, Object[].class)); MethodType.methodType(CallSite.class, BSM zwraca obiekt opakowujący uchwyt do docelowej metody, czyli CallSite. System.out.println( mh.invoke("Confitura") ); Możemy też przekazad naszą intencję, czy chcemy w przyszłości zmieniad 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 przechowywad w swoim kodzie. Można także tworzyd własne klasy specjalizowane CallSite. Typ uchwytu do metody zwracanego przez BSM musi byd 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 przekazad dodatkowe parametry (bsmArgs). W sumie to także nazwa wywoływanej metody („run me”) jest takim dodatkowym parametrem, bo jedyne co musi byd zachowane, to typ uchwytu zwracany z BSM - musi byd zgodny z tym, co określił caller w callsite. 56
  • 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)); Docelowa metoda (target). } catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); } Jej sygnatura (=typ, MethodType) NIE musi byd w 100% zgodna z tym, co return new ConstantCallSite(mh); 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 wymagao (typu) określonego w miejscu wywoływania metody. public static void main(String args[]) throws Throwable { MethodHandle mh = InvokeDynamic.prepare("runTo pewnie oczywiste, ale to co przychodzi jako argument tej metody (tu: me", MethodType.methodType(String.class, String.class), "myBSM", HelloInDyWorld1.class, String s), to są argumenty przekazane w wywołaniu metody (tu: „Confitura”). No chyba że, ciąg kombinatorów to zmodyfikował (, będzie MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class)); później przykład pięknie to pokazujący). System.out.println( mh.invoke("Confitura") ); } } 57
  • 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); Dynamiczne wywołanie metody. } 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") ); } } 58
  • 59. InvokeDynamic.prepare • własny kod pomocniczy (z braku składni w Java dla InvokeDynamic) • pozwala utworzyd 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 59
  • 60. Klasa InvokeDynamic • jej zrozumienie nie jest szczególnie trudne, ale trzeba mied elementarną wiedzę o ASM – lub rozumied 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ład jej metodę 60
  • 61. HelloIndyWorld2 Przykład pokazuje, że do BSM można (z miejsca wywołania !) przekazywad parametry, a tym samym skonfigurowad sposób wywoływania metod z tego miejsca – dynamizm w akcji ! 61
  • 62. DemoIndySecond.HelloIndyWorld2 public 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)); else mh = 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") ); } } 62
  • 63. HelloIndyWorld3 • przykład pokazuje jak uczynid 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… 63
  • 64. DemoIndySecond.HelloIndyWorld3 public 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 !") ); } } 64
  • 65. DEMO III czyli krótki benchmark wywołao metod: InvokeDynamic vs. Static vs. Reflection 65
  • 66. Po co taki (głupawy w sumie) benchmark ? • bo dosyd często podnoszą się głosy, że taki dynamiczny mechanizm wywoływania metod musi byd wolny – „jak pokazuje doświadczenie z refleksją…” • niekoniecznie. – po to przede wszystkim stworzono InvokeDynamic (JSR-292), aby pogodzid dynamizm z wydajnością • a przynajmniej dad JIT na to szansę – z każdym kolejnym uaktualnieniem Java 7 spodziewałbym się też coraz większej wydajności InDy 66
  • 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. statyczny 3. refleksyjny 4. 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); 68 }
  • 69. Wyniki ? 69
  • 70. Benchmark INVOKE DYNAMIC 1999800000000, TIME: 228 ms 1999800000000, TIME: 150 ms 1999800000000, TIME: 127 ms 1999800000000, TIME: 132 ms 1999800000000, TIME: 133 ms 1999800000000, TIME: 140 ms 1999800000000, TIME: 144 ms Benchmark NORMAL (STATIC) 1999800000000, TIME: 142 ms 1999800000000, TIME: 154 ms 1999800000000, TIME: 122 ms 1999800000000, TIME: 135 ms  1999800000000, TIME: 139 ms 1999800000000, TIME: 131 ms 1999800000000, TIME: 126 ms Benchmark REFLECTIVE 1999800000000, TIME: 4513 ms 1999800000000, TIME: 4258 ms 1999800000000, TIME: 4248 ms 1999800000000, TIME: 4290 ms 1999800000000, TIME: 4236 ms 1999800000000, TIME: 4156 ms 1999800000000, TIME: 4195 ms Benchmark REFLECTIVE NO BOXING 1999800000000, TIME: 3077 ms 1999800000000, TIME: 2879 ms 1999800000000, TIME: 2921 ms 1999800000000, TIME: 3011 ms 1999800000000, TIME: 2910 ms 1999800000000, TIME: 3010 ms 1999800000000, TIME: 2845 ms 70
  • 71. DEMO IV czyli przykład wykorzystania jednej z cech BSM 71
  • 72. Ale po co ? • pewnie wciąż zadajecie sobie pytanie, po co ten cały InvokeDynamic ? • odpowiedź wciąż brzmi: – aby pisad bardziej dynamiczny kod  • w tym przykładzie będzie pokazane wykorzystanie jednej z ważnych cech InvokeDynamic (i mojej również) – laziness 72
  • 73. BSM • jak pamiętacie, BSM dla instrukcji INVOKEDYNAMIC jest wykonywana dopiero wtedy, gdy JVM taką instrukcję napotka • czyli można ten fakt wykorzystad do tego, aby bardzo późno wykonywad pewne operacje (np. inicjalizacje) – „późno” = „dopiero gdy jest to potrzebne/używane” 73
  • 74. Przykład – Lazy Constant • załóżmy, że chcecie mied w swojej klasie stałą wartośd (np. XML), ale inicjowaną poprzez wykonanie jakiejś bardziej złożonej logiki – załóżmy, że ta logika potencjalnie może wykonywad się długo • a w Waszej aplikacji czas jest istotny 74
  • 75. Pomysł 1 Stała na poziomie klasy, inicjowana w bloku statycznym public class ConstantResourceImpl implements ConstantResource { public static final String xml; static { xml = ParseHelper.doSomeHeavyParsing("test.xml"); } @Override public void notNeedingTheXMLHere() { //Hey, I am NOT using the 'xml' constant here ! } @Override public void badlyNeedingTheXMLHere() { //I will be using the 'xml' constant here ! try { System.out.println(xml); } catch (Throwable e) { e.printStackTrace(); } } } 75
  • 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(); } } 76
  • 77. Pomysł 2 (bardziej lazy, a w zasadzie to very lazy) • „dostęp do wartości stałej” = „wykonanie metody zwracającej (stałą) wartośd” – 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śd 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 byd określone dynamicznie (= w czasie wykonania) – dodatkowo: podejście z InDy jest bardziej bezpieczne wątkowo ! Patrz 77 gwarancje określone w JVM spec.
  • 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(); } } @Override public void notNeedingTheXMLHere() { //But, I am NOT using the 'xml' constant here ! } @Override public void badlyNeedingTheXMLHere() { //I will be using the 'xml' constant here ! try { System.out.println( mh.invoke()); } catch (Throwable e) { e.printStackTrace(); } } 78 }
  • 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ę. 79
  • 80. A gdy użyjemy stałej… public class LazyConstantsMainWithIndy { public static void main(String[] args) { ConstantResource testObject = new ConstantResourceImplWithIndy("test.xml"); testObject.badlyNeedingTheXMLHere(); } } 80
  • 81. Ten sam trick może byd użyteczny w wielu Waszych programach… 81
  • 83. DEMO V czyli przyspiesz swój kod rekurencyjny (a tak naprawdę, to: jak czerpad intelektualną przyjemnośd z czytania kodu zawierającego InvokeDynamic) (zwłaszcza, gdy tego kodu nie musisz napisad ) 83
  • 84. Czy masz pomysł jak przyspieszyd 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)); } } 84
  • 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… 85
  • 86. Memoization • wykorzystamy dynamiczne wywoływanie metod, aby ten piękny i przejrzysty kod przyspieszyd – 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 86
  • 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  87
  • 88. Ale zanim nastąpią wyjaśnienia, zobaczcie efekt koocowy ! SpeedRecurenceWithIndy.java 88
  • 89. Zresztą, w zasadzie to nawet zmieniliśmy złożonośd obliczeniową tej metody! [z wykładniczej na niemal stałą ;-)] Dla N > 102 kooczy się pojemnośd long (263 -1) … Zamieniając long na BigInteger można spokojnie podad i N = 3000 (ponad 600 cyfrowa liczba). Czas: 89 ~50 ms . Ten przykład też jest na GitHub-ie.
  • 90. OK, to na czym polegają różnice ? 90
  • 91. 1. Zamiast wywołao fib(n) użycie InvokeDynamic public 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)); } 91 }
  • 92. 2. Bufor do przechowywania wyników private static ClassValue<HashMap<String, HashMap<Object, Object>>> cacheTables = new ClassValue<HashMap<String, HashMap<Object, Object>>>() { @Override protected 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śd 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) Notabene: szkoda, że dla klas anonimowych nie można • wynik metody (wartośd) użyd nowego w Java 7 operatora diamond. Fajniej byłoby napisad: „= new ClassValue<>()”, a kompilator 92 się domyślił reszty…
  • 93. 3. Metody pomocnicze dla BSM (i ich uchwyty) 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) 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); } 93 }
  • 94. 4. Bootstrap method (I) Aaaaaaby zamienid statyczne wywołania * fib() + na dynamiczne, wystarczyłoby tyle: 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); } My chcemy jednak „wpiąd się” z bardziej złożoną logiką ! 94
  • 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()); WTF !?! identity = identity.asType(identity.type().changeParameterType(0, Object.class)); identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0)); Spokojnie, będę po kolei całośd wyjaśniad ! MethodHandle update = UPDATE.bindTo(cache); Poznając InvokeDynamic warto poznad jeden trick : update = update.asType(type.insertParameterTypes(0, type.returnType())); TRZEBA CZYTAD OD KOŃCA (czyli od dołu w górę) !!! 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); 95 return new ConstantCallSite(memoize);
  • 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); } Dla przypomnienia: zadaniem BSM jest utworzyd i skonfigurowad miejsce wywołania (callsite). Skonfigurowad, czyli określid docelową MethodHandle identity = MethodHandles.identity(type.returnType()); target. metodę – identity = identity.asType(identity.type().changeParameterType(0, Object.class)); Tutaj, utworzone miejsce wywołania, tj. jego target, mimo, iż będzie identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0)); to całkiem skomplikowany łaocuch operacji, nie będzie się zmieniał, więc jest typu ConstantCallSite. MethodHandle update = UPDATE.bindTo(cache); update = update.asType(type.insertParameterTypes(0, type.returnType())); A to sprzyja optymalizacjom wykonywanym przez JIT. Łaocuch kombinatorów musi zapewnid, że typ uchwytu zwracanego MethodHandle fallback = MethodHandles.foldArguments(update, target); fallback = MethodHandles.dropArguments(fallback, 0, Object.class); memoize) będzie zgodny z typem miejsca wywołania z BSM (tu: (czyli w tym przykładzie: (long)long, bo taki jest typ metody fib). Ale ten BSM jest ładniejszy (bardziej generyczny) – nie ma MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback); operacje w rodzaju „zaszytych” tych typów – stąd type.parameterType(0), type.returnType(), itp. MethodHandle cacheQuerier = MAP_GET.bindTo(cache); cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0))); MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier); 96 return new ConstantCallSite(memoize);
  • 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); Argumentami foldArguments są uchwyty do metod. foldArguments działa w ten String selector = name + type.toMethodDescriptorString(); sposób, że: HashMap<Object, Object> cache = cacheTable.get(selector); 1. najpierw wywołuje uchwyt podany jako drugi argument (tu: cacheQuerier) if (cache == null) { 2. jeśli zwróci on wynik (także null, ale nie void), to ten wynik jest wstawiany cache = new HashMap<Object, Object>(); !) do listy argumentów na pozycji 0 pierwszego uchwytu (tu: (INSERT cacheTable.put(selector, cache); combiner) } 3. wywoływany jest pierwszy uchwyt (tu: combiner) Tutaj, cacheQuerier będzie najpierw odpytywał bufor (za pomocą metody MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, Object.class));tego odpytywania (obiekt/wynik obliczeo lub HashMap.get) i wstawiał wynik null). identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0)); Typ wynikowego uchwytu z foldArguments (tu: memoize) musi byd zgodny z MethodHandle update = UPDATE.bindTo(cache); typem miejsca wywołania *tu: (long)long, bo taki jest typ metody fib+ 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); 97 return new ConstantCallSite(memoize);
  • 98. 4. Bootstrap method (II) public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws ReflectiveOperationException { Zadaniem cacheQuerier jest odpytanie bufora (chod nie jestem przekonany czy ‚cache’ to jest tutaj właściwa nazwa), czy dla danego argumentu (wartości MethodHandle target = lookup.findStatic(staticType, name, type); przekazanej do metody fib), który tutaj jest kluczem, została wcześniej przypisana wartośd (rezultat wcześniejszego wywołania). HashMap<String, HashMap<Object, Object>> cacheTable nie, to zwracany jest null. Jeśli = cacheTables.get(staticType); Jeśli tak, to zwracany jest obiekt (=wstawiony wcześniej, wynik wykonania metody). String selector = name + type.toMethodDescriptorString(); HashMap<Object, Object> cache = cacheTable.get(selector); if (cache == null) { Wracając trochę do poprzedniego slajdu i metody foldArguments: Uchwyt cacheQuerier w czasie wykonania (!) zawsze zwróci wynik różny od cache = new HashMap<Object, Object>(); void (bo tak działa HashMap.get), więc jeśli chodzi o typowanie, to pierwszy cacheTable.put(selector, cache); argument uchwytu combiner będzie pominięty (tak działa foldArguments). } Czyli (tu) typ uchwytu combiner musi byd (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 mied memoize). MethodHandle identity = MethodHandles.identity(type.returnType()); identity = identity.asType(identity.type().changeParameterType(0, cacheQuerier musi mied typ (Object)long *bo taki jest wymóg Kontynuując: Object.class)); foldArguments). cacheQuerier to uchwyt do metody HashMap.get, która ma identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0)); typ (Object)Object. A zatem, za pomocą metody asType, adaptujemy uchwyt cacheQuerier, tak, aby spełnid wymóg foldArguments i „zaprezentowad” MethodHandle update = UPDATE.bindTo(cache); cacheQuerier, jako uchwyt o typie: (Object)long. update = update.asType(type.insertParameterTypes(0, type.returnType())); Gra i buczy… 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); 98 return new ConstantCallSite(memoize);