Invokedynamic 101

Изданието на Java 7 на Oracle въведе нова invokedynamicинструкция за байт код на виртуалната машина Java (JVM) и нов java.lang.invokeAPI пакет в стандартната библиотека на класове. Тази публикация ви запознава с тази инструкция и API.

Какво и как на призованата динамика

В: Какво е invokedynamic?

A: invokedynamic е инструкция за байт код, която улеснява изпълнението на динамични езици (за JVM) чрез динамично извикване на метод. Тази инструкция е описана в изданието Java SE 7 на спецификацията JVM.

Динамични и статични езици

А динамичен език (известен също като динамично типизиран език ) е програмен език на високо ниво, чиято проверка тип обикновено се извършва по време на работа, функция, известен като динамично типизиране . Проверката на типа проверява дали дадена програма е безопасна за типа : всички операционни аргументи имат правилния тип. Groovy, Ruby и JavaScript са примери за динамични езици. ( @groovy.transform.TypeCheckedАнотацията кара Groovy да въведе проверка по време на компилация.)

За разлика от това, статичният език (известен също като статично типизиран език ) извършва проверка на типа по време на компилация, функция, известна като статично писане . Компилаторът проверява дали дадена програма е правилна по тип, въпреки че може да отложи проверка на някакъв тип за времето на изпълнение (помислете за прехвърляния и checkcastинструкциите). Java е пример за статичен език. Компилаторът на Java използва информация за този тип, за да създаде силно типизиран байт код, който може да бъде изпълнен ефективно от JVM.

В: Как invokedynamicулеснява динамичното внедряване на езика?

О: В динамичен език проверката на типа обикновено се случва по време на изпълнение. Разработчиците трябва да преминат подходящи типове или да рискуват неуспехи по време на изпълнение. Често случаят java.lang.Objectе най-точният тип за аргумент на метод. Тази ситуация усложнява проверката на типа, което влияе върху производителността.

Друго предизвикателство е, че динамичните езици обикновено предлагат възможност за добавяне на полета / методи към и премахване от съществуващите класове. В резултат на това е необходимо да се отложи клас, метод и разделителна способност на полето за изпълнение. Също така често е необходимо да се адаптира извикването на метод към цел, която има различен подпис.

Тези предизвикателства традиционно изискват ad hoc поддръжка за изпълнение, която да бъде изградена върху JVM. Тази поддръжка включва класове тип обвивка, използване на хеш таблици за осигуряване на динамична разделителна способност на символи и т.н. Байтовият код се генерира с входни точки към времето на изпълнение под формата на извиквания на методи, използвайки някоя от четирите инструкции за извикване на метод:

  • invokestaticсе използва за извикване на staticметоди.
  • invokevirtualсе използва за извикване publicи protectedнеметоди staticчрез динамично изпращане.
  • invokeinterfaceе подобно на invokevirtualс изключение на изпращането на метод, което се основава на тип интерфейс.
  • invokespecialсе използва за извикване на методи за инициализация на екземпляри (конструктори), както и privateметоди и методи на суперклас от текущия клас.

Тази поддръжка за изпълнение влияе върху производителността. Генерираният байт код често изисква няколко действителни извиквания на JVM метод за едно извикване на динамичен езиков метод. Отражението се използва широко и допринася за влошаване на производителността. Също така, многото различни пътища за изпълнение правят невъзможно JVM-то точно навреме (JIT) компилатор да прилага оптимизации.

За да се справи с лошата производителност, invokedynamicинструкцията премахва ad hoc поддръжката по време на изпълнение. Вместо това първото извикване на bootstraps чрез извикване на логика по време на изпълнение, която ефективно избира целевия метод, а следващите повиквания обикновено извикват целевия метод, без да се налага повторно зареждане.

invokedynamicсъщо облагодетелства внедряващите динамични езици, като поддържа динамично променящи се цели на сайта за повикване - сайтът за повикване , по-точно, динамичният сайт за повикване е invokedynamicинструкция. Освен това, тъй като JVM вътрешно поддържа invokedynamic, тази инструкция може да бъде по-добре оптимизирана от JIT компилатора.

Метод дръжки

Въпрос: Разбирам, че invokedynamicработи с манипулатори на методи, за да улесни динамичното извикване на метод. Какво представлява методът за обработка?

A: Манипулаторът на метода е "типизирана, директно изпълнима препратка към основен метод, конструктор, поле или подобна операция от ниско ниво, с незадължителни трансформации на аргументи или върнати стойности." С други думи, той е подобен на показалеца на функция в стил С, който сочи към изпълним код - цел - и който може да бъде дереферендиран, за да извика този код. Манипулаторите на методите са описани от абстрактния java.lang.invoke.MethodHandleклас.

Въпрос: Можете ли да предоставите прост пример за създаване и извикване на манипулатор на метод?

О: Вижте листинг 1.

Листинг 1. MHD.java(версия 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"); } }

Листинг 1 описва демонстрационна програма за обработка на методи, състояща се от main()и hello()методи на клас. Целта на тази програма е да извиква hello()чрез манипулатор на метод.

main()Първата задача е да се получи java.lang.invoke.MethodHandles.Lookupобект. Този обект е фабрика за създаване на манипулатори на методи и се използва за търсене на цели като виртуални методи, статични методи, специални методи, конструктори и достъпни полета. Освен това, това зависи от контекста на извикване на сайта за повикване и налага ограничения за достъп на манипулатора на метода всеки път, когато се създава манипулатор на метод. С други думи, сайтът за повиквания (като main()метода от Листинг 1, действащ като сайт за повикване), който получава обект за търсене, може да има достъп само до тези цели, които са достъпни за сайта за повикване. Справочният обект се получава чрез извикване на метода на java.lang.invoke.MethodHandlesкласа MethodHandles.Lookup lookup().

publicLookup()

MethodHandlesсъщо декларира MethodHandles.Lookup publicLookup()метод. За разлика от това lookup(), което може да се използва за получаване на манипулатор на метод към всеки достъпен метод / конструктор или поле, publicLookup()може да се използва за получаване на манипулатор на метод до публично достъпно поле или само публично достъпен метод / конструктор.

След получаване на обекта за търсене, MethodHandle findStatic(Class refc, String name, MethodType type)методът на този обект се извиква за получаване на манипулатор на hello()метода към метода. Първият аргумент, предаден на, findStatic()е препратка към класа ( MHD), от който hello()се осъществява достъп до метода ( ), а вторият аргумент е името на метода. Третият аргумент е пример за тип метод , който "представлява аргументите и типа на връщане, приети и върнати от манипулатор на метод, или аргументите и типа на връщане, предадени и очаквани от повикващия на манипулатора на метода." Той е представен от екземпляр на java.lang.invoke.MethodTypeкласа и е получен (в този пример) чрез извикване java.lang.invoke.MethodTypeна MethodType methodType(Class rtype)метода на. Този метод се извиква, защото hello()осигурява само тип връщане, какъвто е случаятvoid. Този тип връщане се предоставя на разположение methodType()чрез преминаване void.classкъм този метод.

Върнатият манипулатор на метода е присвоен на mh. След това този обект се използва за извикване MethodHandleна Object invokeExact(Object... args)метода, за извикване на манипулатора на метода. С други думи, invokeExact()резултатите hello()са извикани и helloзаписани в стандартния изходен поток. Тъй като invokeExact()е обявено за хвърляне Throwable, аз съм добавил throws Throwableкъм main()заглавката на метода.

Въпрос: В предишния си отговор споменахте, че справочният обект може да има достъп само до тези цели, които са достъпни до сайта за обаждания. Можете ли да предоставите пример, който демонстрира опит за получаване на манипулатор на метод към недостъпна цел?

О: Вижте списък 2.

Листинг 2. MHD.java(версия 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)); } }

Листинг 2 декларира HW(Hello, World) и MHDкласове. HWдекларира publichello1()метод на privatehello2()екземпляр и метод на екземпляр. MHDдекларира main()метод, който ще се опита да извика тези методи.

main()Първата задача е да се създаде екземпляр HWв подготовка за извикване hello1()и hello2(). След това той получава обект за търсене и използва този обект, за да получи манипулатор на метод за извикване hello1(). Този път MethodHandles.Lookupе findVirtual()метод се нарича и в първия аргумент на този метод е Classобект, описващ HWклас.

Оказва се, че findVirtual()ще успее и последващият mh.invoke(hw);израз ще извика hello1(), в резултат на което ще hello from hello1бъде изведен.

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?

О: Създадох пример, базиран на метода на Mathкласа на double pow(double a, double b)класа. В този пример получавам манипулатор на pow()метод към метода и трансформирам този манипулатор на метода, така че вторият аргумент, предаден на, да pow()е винаги 10. Вижте списък 4.