Co jest invokedynamic i jak go używać?

159

Ciągle słyszę o wszystkich nowych fajnych funkcjach, które są dodawane do JVM, a jedną z tych fajnych funkcji jest wywołanie dynamiki. Chciałbym wiedzieć, co to jest i jak sprawia, że ​​refleksyjne programowanie w Javie jest łatwiejsze lub lepsze?

David K.
źródło

Odpowiedzi:

165

Jest to nowa instrukcja JVM, która pozwala kompilatorowi na generowanie kodu, który wywołuje metody o luźniejszej specyfikacji niż było to wcześniej możliwe - jeśli wiesz, co to jest " kaczkowate pisanie ", invokedynamic w zasadzie pozwala na kaczkę. Jako programista Java nie może z tym zrobić zbyt wiele; Jeśli jednak jesteś twórcą narzędzi, możesz go użyć do tworzenia bardziej elastycznych i wydajnych języków opartych na JVM. Oto naprawdę słodki wpis na blogu, który zawiera wiele szczegółów.

Ernest Friedman-Hill
źródło
3
W codziennym programowaniu w języku Java nierzadko zdarza się, że odbicie jest używane do dynamicznego wywoływania metod w programie meth.invoke(args). Więc jak to invokedynamicpasuje meth.invoke?
David K.,
1
We wpisie na blogu MethodHandle, o którym wspominam, jest mowa o tym samym rodzaju, ale z dużo większą elastycznością. Ale prawdziwa siła tkwi w tym wszystkim nie w dodatkach do języka Java, ale w możliwościach samej maszyny JVM we wspieraniu innych języków, które są z natury bardziej dynamiczne.
Ernest Friedman-Hill,
1
Wygląda na to, że Java 8 tłumaczy niektóre lambdy, używając, invokedynamicco czyni ją wydajną (w porównaniu z opakowaniem ich w anonimową klasę wewnętrzną, która była prawie jedynym wyborem przed wprowadzeniem invokedynamic). Najprawdopodobniej wiele funkcjonalnych języków programowania korzystających z JVM zdecyduje się na kompilację do tego zamiast do anon-internal-classes.
Nader Ghanbari,
2
Małe ostrzeżenie, że post na blogu z 2008 roku jest beznadziejnie przestarzały i nie odzwierciedla faktycznego stanu wydania (2011).
Holger,
9

Jakiś czas temu C # dodał fajną funkcję, dynamiczną składnię w C #

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

Potraktuj to jako lukę składniową dla wywołań metod refleksyjnych. Może mieć bardzo ciekawe zastosowania. zobacz http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter

Neal Gafter, który jest odpowiedzialny za dynamiczny typ C #, właśnie przeszedł z SUN do MS. Nie jest więc nierozsądne myślenie, że te same rzeczy zostały omówione w SUN.

Pamiętam, że wkrótce potem jakiś koleś z Javy ogłosił coś podobnego

InvokeDynamic duck = obj;
duck.quack(); 

Niestety, tej funkcji nie ma w Javie 7. Bardzo rozczarowany. Programiści Java nie mają łatwego sposobu wykorzystania invokedynamicich w swoich programach.

niepodważalny
źródło
41
invokedynamicnigdy nie był przeznaczony do użytku przez programistów Java. IMO w ogóle nie pasuje do filozofii Java. Został dodany jako funkcja JVM dla języków innych niż Java.
Mark Peters
5
@Mark Nigdy nie zamierzony przez kogo? To nie jest tak, że istnieje wyraźna struktura władzy u celebrytów języka Java, ani dobrze zdefiniowana zbiorowa „intencja”. Jeśli chodzi o filozofię językową - jest to całkiem wykonalne, patrz wyjaśnienie Neal Gafter (zdrajca!): Infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
bezrefleksyjne
3
@mark peters: invokedynamic jest w rzeczywistości przeznaczony również dla programistów Java, ale nie jest bezpośrednio dostępny. To podstawa zamknięć Java 8.
M Platvoet
2
@irreputable: nigdy nie zamierzone przez autorów JSR. Mówi się, że nazwa JSR to „Wspieranie języków z dynamicznym typowaniem na platformie Java”. Java nie jest językiem dynamicznie typowanym.
Mark Peters,
5
@M Platvoet: Nie byłem na bieżąco z zamknięciami, ale z pewnością nie byłby to absolutny wymóg dla zamknięć. Inną opcją, którą omawiali, było po prostu zrobienie zamknięcia składniowego cukru dla anonimowych klas wewnętrznych, co można było zrobić bez zmiany specyfikacji VM. Chodziło mi jednak o to, że JSR nigdy nie miało na celu przeniesienia dynamicznego pisania do języka Java, to jest jasne, jeśli przeczytasz JSR.
Mark Peters,
4

Przed kontynuacją przywoływania dynamiki należy zrozumieć dwie koncepcje.

1. Typowanie statyczne a Dynamin

Statyczny - sprawdzanie typu preform w czasie kompilacji (np. Java)

Dynamiczne - sprawdzanie typu preform w czasie wykonywania (np. JavaScript)

Sprawdzanie typu to proces sprawdzania, czy program jest bezpieczny, czyli sprawdzanie wpisanych informacji pod kątem zmiennych klas i instancji, parametrów metod, wartości zwracanych i innych zmiennych. Np. Java wie o int, String, .. w czasie kompilacji, podczas gdy typ obiektu w JavaScript można określić tylko w czasie wykonywania

2. Silne i słabe pisanie

Strong - określa ograniczenia dotyczące typów wartości dostarczanych do jego operacji (np. Java)

Weak - konwertuje (rzuca) argumenty operacji, jeśli te argumenty mają niekompatybilne typy (np. Visual Basic)

Wiedząc, że Java jest typem statycznym i słabo typowanym, jak zaimplementować języki dynamicznie i silnie typowane w JVM?

Invokedynamic implementuje system wykonawczy, który może wybrać najbardziej odpowiednią implementację metody lub funkcji - po skompilowaniu programu.

Przykład: mając (a + b) i nie wiedząc nic o zmiennych a, b w czasie kompilacji, invokedynamic mapuje tę operację na najbardziej odpowiednią metodę w Javie w czasie wykonywania. Na przykład, jeśli okaże się, że a, b są ciągami znaków, wywołaj metodę (String a, String b). Jeśli okaże się, że a, b są ints, wywołaj metodę (int a, int b).

invokedynamic został wprowadzony w Javie 7.

Sabina Orazem
źródło
4

W ramach mojego artykułu o Java Records wypowiedziałem się na temat motywacji stojącej za Inoke Dynamic. Zacznijmy od przybliżonej definicji Indy.

Przedstawiamy Indy

Invoke Dynamic (znany również jako Indy ) był częścią JSR 292, którego celem było ulepszenie obsługi JVM dla dynamicznych języków typu. Po pierwszym wydaniu w Javie 7 invokedynamickod operacji wraz z jego java.lang.invokebagażem jest używany dość intensywnie przez dynamiczne języki oparte na JVM, takie jak JRuby.

Chociaż indy został specjalnie zaprojektowany, aby zwiększyć obsługę dynamicznego języka, oferuje znacznie więcej. W rzeczywistości nadaje się do użycia wszędzie tam, gdzie projektant języka potrzebuje jakiejkolwiek formy dynamiki, od dynamicznej akrobatyki po dynamiczne strategie!

Na przykład, wyrażenia Lambda Java 8 są faktycznie implementowane przy użyciu invokedynamic, mimo że Java jest językiem z typowaniem statycznym!

Kod bajtowy definiowany przez użytkownika

Przez jakiś czas JVM obsługiwała cztery typy wywołań metod: invokestaticwywoływanie metod statycznych, invokeinterfacewywoływanie metod interfejsu, invokespecialwywoływanie konstruktorów super()lub metody prywatne i invokevirtualwywoływanie metod instancji.

Pomimo różnic, te typy inwokacji mają jedną wspólną cechę: nie możemy ich wzbogacić naszą własną logiką . Wręcz przeciwnie, invokedynamic umożliwia nam załadowanie procesu wywołania w dowolny sposób. Następnie JVM dba o bezpośrednie wywołanie metody Bootstrapped.

Jak działa Indy?

Gdy maszyna JVM po raz pierwszy widzi invokedynamicinstrukcję, wywołuje specjalną metodę statyczną o nazwie Metoda Bootstrap . Metoda bootstrap to fragment kodu Java, który napisaliśmy, aby przygotować rzeczywistą logikę do wywołania:

wprowadź opis obrazu tutaj

Następnie metoda bootstrap zwraca wystąpienie java.lang.invoke.CallSite. Odnosi się CallSiteto do faktycznej metody, tj MethodHandle.

Odtąd za każdym razem, gdy JVM invokedynamicponownie zobaczy tę instrukcję, pomija powolną ścieżkę i bezpośrednio wywołuje podstawowy plik wykonywalny. JVM nadal omija powolną ścieżkę, chyba że coś się zmieni.

Przykład: Java 14 Records

Java 14 Recordszapewnia ładną, zwartą składnię do deklarowania klas, które mają być głupimi posiadaczami danych.

Biorąc pod uwagę ten prosty rekord:

public record Range(int min, int max) {}

Kod bajtowy dla tego przykładu wyglądałby tak:

Compiled from "Range.java"
public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #18,  0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
         6: areturn

W tabeli metod Bootstrap :

BootstrapMethods:
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
     (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
     Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
     Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
    Method arguments:
      #8 Range
      #48 min;max
      #50 REF_getField Range.min:I
      #51 REF_getField Range.max:I

Dlatego wywoływana jest metoda ładowania dla rekordów, bootstrapktóra znajduje się w java.lang.runtime.ObjectMethodsklasie. Jak widać, ta metoda ładowania początkowego wymaga następujących parametrów:

  • Wystąpienie MethodHandles.Lookupreprezentujące kontekst wyszukiwania ( Ljava/lang/invoke/MethodHandles$Lookupczęść).
  • Nazwa metody (tj toString, equals, hashCode, itd.) Bootstrap będzie link. Na przykład, gdy wartością jest toString, funkcja bootstrap zwróci ConstantCallSite(a, CallSitektóre nigdy się nie zmienia), które wskazuje na rzeczywistą toStringimplementację tego konkretnego rekordu.
  • TypeDescriptorDla sposobu ( Ljava/lang/invoke/TypeDescriptor część).
  • Token typu, tj. Class<?>Reprezentujący typ klasy Record. Tak jest Class<Range>w tym przypadku.
  • Lista wszystkich nazw komponentów oddzielonych średnikami, np min;max.
  • Jeden MethodHandlena składnik. W ten sposób metoda bootstrap może utworzyć na MethodHandlepodstawie komponentów tej konkretnej implementacji metody.

invokedynamicInstrukcja przechodzi wszystkie te argumenty metody bootstrap. Z kolei metoda Bootstrap zwraca instancję ConstantCallSite. Ten ConstantCallSitetrzyma odniesienie do żądanej metody realizacji, np toString.

Dlaczego Indy?

W przeciwieństwie do interfejsów API Reflection, interfejs java.lang.invokeAPI jest dość wydajny, ponieważ maszyna JVM może w pełni przejrzeć wszystkie wywołania. Dlatego JVM może stosować wszelkiego rodzaju optymalizacje, o ile unikamy powolnej ścieżki tak długo, jak to możliwe!

Oprócz argumentu dotyczącego wydajności, invokedynamicpodejście to jest bardziej niezawodne i mniej kruche ze względu na swoją prostotę .

Ponadto generowany kod bajtowy dla Java Records jest niezależny od liczby właściwości. Tak więc mniej kodu bajtowego i krótszy czas uruchamiania.

Na koniec załóżmy, że nowa wersja Java zawiera nową i bardziej wydajną implementację metody ładowania początkowego. Dzięki invokedynamicnaszej aplikacji możemy skorzystać z tego ulepszenia bez ponownej kompilacji. W ten sposób mamy pewnego rodzaju kompatybilność binarną do przodu . Poza tym to dynamiczna strategia, o której mówiliśmy!

Inne przykłady

Oprócz Java Records, dynamika wywołania została wykorzystana do implementacji funkcji takich jak:

Ali Dehghani
źródło