Invokedynamic 101

Oracleovo izdanje Java 7 predstavilo je nove invokedynamicupute bytecodea za Java Virtual Machine (JVM) i novi java.lang.invokeAPI paket u standardnu ​​knjižnicu klasa. Ovaj vas post upoznaje s ovom uputom i API-jem.

Što i kako invokedynamic

P: Što je invokedynamic?

O: invokedynamic je uputa bytecode-a koja olakšava implementaciju dinamičkih jezika (za JVM) kroz pozivanje dinamičke metode. Ova je uputa opisana u izdanju Java SE 7 JVM specifikacije.

Dinamički i statički jezici

Dinamičan jezik (također poznat kao dinamički upisali jeziku ) je programski jezik visoke razine čija tip provjere se obično obavlja na vrijeme izvođenja, značajka poznat kao dinamičan tipkanje . Provjera tipa provjerava je li program siguran za tip : svi argumenti operacija imaju ispravan tip. Groovy, Ruby i JavaScript primjeri su dinamičnih jezika. ( @groovy.transform.TypeCheckedNapomena uzrokuje da Groovy upiše provjeru u vrijeme sastavljanja.)

Suprotno tome, statički jezik (poznat i kao statički otkucan jezik ) vrši provjeru tipa u vrijeme sastavljanja, značajka poznata kao statičko tipkanje . Prevoditelj provjerava je li program točan, iako može odgoditi provjeru neke vrste na vrijeme izvođenja (mislite na uloge i checkcastupute). Java je primjer statičkog jezika. Java prevodilac koristi informacije o ovom tipu za izradu snažno otkucanih bajt-kodova, koje JVM može učinkovito izvršiti.

P: Kako invokedynamicolakšava dinamičku implementaciju jezika?

O: U dinamičnom jeziku provjera tipova obično se događa tijekom izvođenja. Programeri moraju proći odgovarajuće vrste ili riskirati neuspjehe u izvođenju. Često je slučaj java.lang.Objectnajtočniji tip za argument metode. Ova situacija komplicira provjeru tipa, što utječe na performanse.

Drugi je izazov taj što dinamički jezici obično nude mogućnost dodavanja polja / metoda i uklanjanje iz postojećih klasa. Kao rezultat toga, potrebno je preusmjeriti klasu, metodu i razlučivost polja za vrijeme izvođenja. Također je često potrebno prilagoditi pozivanje metode cilju koji ima drugačiji potpis.

Ti su izazovi tradicionalno zahtijevali ad hoc podršku za vrijeme izvođenja koja se temelji na JVM-u. Ova podrška uključuje klase omotača, upotrebu hash tablica za pružanje dinamičke razlučivosti simbola i tako dalje. Bytecode se generira s ulaznim točkama za vrijeme izvođenja u obliku poziva metode koristeći bilo koju od četiri upute za pozivanje metode:

  • invokestatickoristi se za pozivanje staticmetoda.
  • invokevirtualkoristi se za pozivanje publici protectedne- staticmetode putem dinamičke otpreme.
  • invokeinterfaceje sličan, invokevirtualosim što se otpremanje metode temelji na tipu sučelja.
  • invokespecialkoristi se za pozivanje metoda inicijalizacije instance (konstruktora) kao i privatemetoda i metoda superklase trenutne klase.

Ova podrška za izvršavanje utječe na performanse. Generirani bytecode često zahtijeva nekoliko stvarnih poziva JVM metode za jedan poziv dinamičke metode jezika. Refleksija se široko koristi i pridonosi pogoršanju performansi. Također, mnogi različiti putovi izvršenja onemogućuju JVM-ovom pravodobnom (JIT) prevoditelju primjenu optimizacija.

Kako bi se riješila loša izvedba, invokedynamicuputa uklanja ad hoc podršku za vrijeme izvođenja. Umjesto toga, prvi poziv bootstraps pozivanjem runtime logike koja učinkovito odabire ciljnu metodu, a sljedeći pozivi obično pozivaju ciljnu metodu bez ponovnog pokretanja.

invokedynamickoristi i implementatorima dinamičkih jezika podržavajući dinamično mijenjanje ciljeva web mjesta poziva - web mjesto poziva , točnije, mjesto dinamičnog poziva je invokedynamicuputa. Nadalje, budući da JVM interno podržava invokedynamic, JIT prevodilac ovu naputku može bolje optimizirati.

Metode ručke

P: Razumijem da to invokedynamicfunkcionira s ručicama metode kako bi se olakšalo dinamično pozivanje metode. Što je ručka metode?

O: metoda ručka je „upisali izravno izvršna pozivanje na temeljne metode, konstruktora, polja, ili sličnog rada niske razine, s dodatnim transformacije argumenata ili vrijednosti povratka.” Drugim riječima, sličan je pokazivaču funkcije u stilu C koji upućuje na izvršni kôd - cilj - i koji se može dereferencirati za pozivanje ovog koda. Ručke metoda opisane su u apstraktnoj java.lang.invoke.MethodHandleklasi.

P: Možete li pružiti jednostavan primjer izrade i pozivanja ručice metode?

O: Pogledajte popis 1.

Popis 1. MHD.java(verzija 1)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; public class MHD { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.findStatic(MHD.class, "hello", MethodType.methodType(void.class)); mh.invokeExact(); } static void hello() { System.out.println("hello"); } }

Popis 1 opisuje demonstracijski program obrade metoda koji se sastoji od metoda main()i hello()klase. Cilj ovog programa je pozivanje hello()putem ručice metode.

main()Prvi je zadatak dobiti java.lang.invoke.MethodHandles.Lookupobjekt. Ovaj je objekt tvornica za stvaranje ručica metoda i koristi se za traženje ciljeva kao što su virtualne metode, statičke metode, posebne metode, konstruktori i pristupnici polja. Nadalje, to ovisi o kontekstu pozivanja web mjesta poziva i provodi ograničenja pristupa obradi metode svaki put kad se kreira obrada metode. Drugim riječima, mjesto poziva (poput main()metode popisa 1 koja djeluje kao mjesto poziva) koje dobiva objekt pretraživanja može pristupiti samo onim ciljevima koji su dostupni mjestu poziva. Objekt pretraživanja dobiva se pozivanjem metode java.lang.invoke.MethodHandlesklase MethodHandles.Lookup lookup().

publicLookup()

MethodHandlestakođer deklarira MethodHandles.Lookup publicLookup()metodu. Za razliku od toga lookup(), koje se može koristiti za dobivanje ručice metode bilo kojoj dostupnoj metodi / konstruktoru ili polju, publicLookup()može se koristiti za dobivanje ručice metode u javno dostupnom polju ili samo javno dostupnoj metodi / konstruktoru.

Nakon dobivanja objekta pretraživanja, MethodHandle findStatic(Class refc, String name, MethodType type)metoda ovog objekta poziva se kako bi se dobila obrada hello()metode. Prvi argument prosljeđen findStatic()referenci je na klasu ( MHD) iz koje se pristupa metodi ( hello()), a drugi argument je ime metode. Treći je argument primjer vrste metode , koja "predstavlja argumente i vrstu povratka prihvaćene i vraćene pomoću ručice metode, ili argumente i vrstu povratka proslijeđene i očekivane od strane pozivatelja ručke metode." To je zastupao instancu java.lang.invoke.MethodTypeklase, a dobiveni (u ovom primjeru) pozivom java.lang.invoke.MethodType„s MethodType methodType(Class rtype)metodu. Ova se metoda naziva jer hello()pruža samo povratni tip, što se i događavoid. Ova vrsta povrata dostupna methodType()je prelaskom void.classna ovu metodu.

Vraćena ručka metode dodjeljuje se mh. Ovaj objekt je zatim koristi za poziv MethodHandleje Object invokeExact(Object... args)metoda, da se pozove na ručku metoda. Drugim riječima, invokeExact()rezultira hello()pozivom i helloupisom u standardni izlazni tok. Budući da invokeExact()je proglašen za baciti Throwable, ja sam u prilogu throws Throwablena main()metodi napadača.

P: U svom prethodnom odgovoru spomenuli ste da objekt pretraživanja može pristupiti samo onim ciljevima koji su dostupni mjestu poziva. Možete li pružiti primjer koji pokazuje pokušaj dobivanja ručice metode do nepristupačne mete?

O: Pogledajte popis 2.

Popis 2. MHD.java(verzija 2)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; class HW { public void hello1() { System.out.println("hello from hello1"); } private void hello2() { System.out.println("hello from hello2"); } } public class MHD { public static void main(String[] args) throws Throwable { HW hw = new HW(); MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.findVirtual(HW.class, "hello1", MethodType.methodType(void.class)); mh.invoke(hw); mh = lookup.findVirtual(HW.class, "hello2", MethodType.methodType(void.class)); } }

Listing 2 declares HW (Hello, World) and MHD classes. HW declares a publichello1() instance method and a privatehello2() instance method. MHD declares a main() method that will attempt to invoke these methods.

main()'s first task is to instantiate HW in preparation for invoking hello1() and hello2(). Next, it obtains a lookup object and uses this object to obtain a method handle for invoking hello1(). This time, MethodHandles.Lookup's findVirtual() method is called and the first argument passed to this method is a Class object describing the HW class.

It turns out that findVirtual() will succeed, and the subsequent mh.invoke(hw); expression will invoke hello1(), resulting in hello from hello1 being output.

Because hello1() is public, it's accessible to the main() method call site. In contrast, hello2() isn't accessible. As a result, the second findVirtual() invocation will fail with an IllegalAccessException.

When you run this application, you should observe the following output:

hello from hello1 Exception in thread "main" java.lang.IllegalAccessException: member is private: HW.hello2()void, from MHD at java.lang.invoke.MemberName.makeAccessException(MemberName.java:507) at java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:1172) at java.lang.invoke.MethodHandles$Lookup.checkMethod(MethodHandles.java:1152) at java.lang.invoke.MethodHandles$Lookup.accessVirtual(MethodHandles.java:648) at java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:641) at MHD.main(MHD.java:27)

Q: Listings 1 and 2 use the invokeExact() and invoke() methods to execute a method handle. What's the difference between these methods?

A: Although invokeExact() and invoke() are designed to execute a method handle (actually, the target code to which the method handle refers), they differ when it comes to performing type conversions on arguments and the return value. invokeExact() doesn't perform automatic compatible-type conversion on arguments. Its arguments (or argument expressions) must be an exact type match to the method signature, with each argument provided separately, or all arguments provided together as an array. invoke() requires its arguments (or argument expressions) to be a type-compatible match to the method signature -- automatic type conversions are performed, with each argument provided separately, or all arguments provided together as an array.

Q: Can you provide me with an example that shows how to invoke an instance field's getter and setter?

A: Check out Listing 3.

Listing 3. MHD.java (version 3)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; class Point { int x; int y; } public class MHD { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); Point point = new Point(); // Set the x and y fields. MethodHandle mh = lookup.findSetter(Point.class, "x", int.class); mh.invoke(point, 15); mh = lookup.findSetter(Point.class, "y", int.class); mh.invoke(point, 30); mh = lookup.findGetter(Point.class, "x", int.class); int x = (int) mh.invoke(point); System.out.printf("x = %d%n", x); mh = lookup.findGetter(Point.class, "y", int.class); int y = (int) mh.invoke(point); System.out.printf("y = %d%n", y); } }

Listing 3 introduces a Point class with a pair of 32-bit integer instance fields named x and y. Each field's setter and getter is accessed by calling MethodHandles.Lookup's findSetter() and findGetter() methods, and the resulting MethodHandle is returned. Each of findSetter() and findGetter() requires a Class argument that identifies the field's class, the field's name, and a Class object that identifies the field's signature.

The invoke() method is used to execute a setter or getter-- behind the scenes, the instance fields are accessed via the JVM's putfield and getfield instructions. This method requires that a reference to the object whose field is being accessed be passed as the initial argument. For setter invocations, a second argument, consisting of the value being assigned to the field, also must be passed.

When you run this application, you should observe the following output:

x = 15 y = 30

Q: Your definition of method handle includes the phrase "with optional transformations of arguments or return values". Can you provide an example of argument transformation?

O: Ja sam stvorio primjer temelji na Mathklasi je double pow(double a, double b)metoda klase. U ovom primjeru dobivam metodu za obradu pow()metode i transformiram je za obradu metode tako da drugi argument koji se prosljeđuje pow()uvijek bude 10. Pogledajte popis 4.