Invokedynamic 101

Oracle의 Java 7 릴리스는 invokedynamicJVM (Java Virtual Machine)에 대한 새로운 바이트 코드 명령과 java.lang.invoke표준 클래스 라이브러리에 대한 새로운 API 패키지를 도입했습니다. 이 게시물에서는이 지침과 API를 소개합니다.

invokedynamic의 내용과 방법

Q : 무엇입니까 invokedynamic?

A : invokedynamic 동적 메서드 호출을 통해 동적 언어 (JVM 용)의 구현을 용이하게하는 바이트 코드 명령어입니다. 이 지침은 JVM 사양의 Java SE 7 Edition에 설명되어 있습니다.

동적 및 정적 언어

동적 언어 (이라고도 동적 타입 언어 ), 형태 확인 통상 실행시 수행되는 높은 수준의 프로그래밍 언어로 알려진 특징이다 동적 타이핑 . 유형 검사는 프로그램이 유형에 안전한지 확인합니다 . 모든 작업 인수의 유형이 올바른지 확인합니다. Groovy, Ruby 및 JavaScript는 동적 언어의 예입니다. ( @groovy.transform.TypeChecked주석으로 인해 Groovy는 컴파일 타임에 유형 검사를 수행합니다.)

반대로 정적 언어 ( 정적으로 형식화 된 언어 라고도 함 )는 컴파일 타임에 형식 검사를 수행합니다.이 기능을 정적 형식 지정이라고 합니다. 컴파일러는 프로그램의 유형이 올바른지 확인하지만 일부 유형 검사를 런타임에 연기 할 수 있습니다 (캐스트 및 checkcast명령어를 생각해보십시오 ). Java는 정적 언어의 예입니다. Java 컴파일러는이 유형 정보를 사용하여 JVM에서 효율적으로 실행할 수있는 강력한 유형의 바이트 코드를 생성합니다.

Q :invokedynamic 동적 언어 구현을 어떻게 용이하게합니까?

A : 동적 언어에서 유형 검사는 일반적으로 런타임에 발생합니다. 개발자는 적절한 유형을 전달하거나 런타임 오류의 위험이 있어야합니다. java.lang.Object메서드 인수에 대해 가장 정확한 유형 인 경우가 종종 있습니다. 이 상황은 유형 검사를 복잡하게하여 성능에 영향을줍니다.

또 다른 문제는 동적 언어가 일반적으로 기존 클래스에 필드 / 메소드를 추가하고 제거하는 기능을 제공한다는 것입니다. 따라서 클래스, 메서드 및 필드 확인을 런타임으로 연기해야합니다. 또한 다른 서명을 가진 대상에 메서드 호출을 적용해야하는 경우가 많습니다.

이러한 문제는 전통적으로 JVM을 기반으로하는 임시 런타임 지원이 필요했습니다. 이 지원에는 래퍼 유형 클래스, 동적 기호 확인을 제공하기 위해 해시 테이블 사용 등이 포함됩니다. 바이트 코드는 네 가지 메서드 호출 명령어 중 하나를 사용하여 메서드 호출 형식으로 런타임에 대한 진입 점으로 생성됩니다.

  • invokestaticstatic메서드 를 호출 하는 데 사용됩니다 .
  • invokevirtual동적 디스패치를 ​​통해 호출 publicprotectedstatic메소드에 사용됩니다 .
  • invokeinterfaceinvokevirtual인터페이스 유형을 기반으로하는 메소드 디스패치 를 제외하고 는 유사 합니다.
  • invokespecial인스턴스 초기화 메서드 (생성자)는 물론 private현재 클래스의 수퍼 클래스의 메서드 및 메서드 를 호출하는 데 사용됩니다 .

이 런타임 지원은 성능에 영향을줍니다. 생성 된 바이트 코드에는 종종 하나의 동적 언어 메소드 호출에 대해 여러 실제 JVM 메소드 호출이 필요합니다. 반사는 널리 사용되며 성능 저하에 기여합니다. 또한 다양한 실행 경로로 인해 JVM의 JIT (Just-In-Time) 컴파일러가 최적화를 적용 할 수 없습니다.

성능 저하를 해결하기 위해 invokedynamic명령은 임시 런타임 지원을 제거합니다. 대신, 첫 번째 호출 은 대상 메서드를 효율적으로 선택하는 런타임 논리를 호출하여 부트 스트랩 하고 후속 호출은 일반적으로 다시 부트 스트랩하지 않고도 대상 메서드를 호출합니다.

invokedynamic또한 동적으로 변경되는 호출 사이트 대상을 지원함으로써 동적 언어 구현 자에게 도움이됩니다. 즉, 호출 사이트 ,보다 구체적으로 동적 호출 사이트invokedynamic명령입니다. 또한 JVM이 내부적으로를 지원하기 invokedynamic때문에이 명령어는 JIT 컴파일러에 의해 더 잘 최적화 될 수 있습니다.

메서드 핸들

Q :invokedynamic 동적 메서드 호출을 용이하게하기 위해 메서드 핸들과 함께 작동 한다는 것을 알고 있습니다. 메서드 핸들이란 무엇입니까?

A :방법 핸들은 "인수 또는 반환 값을 선택적으로 변환하는 기본 메소드, 생성자, 필드 또는 유사한 낮은 수준의 작업에 입력, 직접 실행 참조,"입니다. 즉, 실행 가능한 코드 ( 타겟) 를 가리키는 C 스타일 함수 포인터와 유사 하며이 코드를 호출하기 위해 역 참조 할 수 있습니다. 메서드 핸들은 추상 java.lang.invoke.MethodHandle클래스에서 설명합니다 .

Q : 메서드 핸들 생성 및 호출의 간단한 예를 제공 할 수 있습니까?

A : 목록 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.MethodTypeMethodType methodType(Class rtype)메서드 를 호출하여 가져 옵니다 (이 예에서는) . 이 메서드는 hello()반환 유형 만 제공 하기 때문에 호출 됩니다.void. 이 반환 유형은 이 메서드 methodType()에 전달 void.class하여 사용할 수 있습니다 .

반환 된 메서드 핸들은에 할당됩니다 mh. 그런 다음이 개체는 MethodHandleObject invokeExact(Object... args)메서드를 호출하여 메서드 핸들을 호출하는 데 사용됩니다 . 즉, invokeExact()결과는 hello()호출되고, 그리고 hello표준 출력 스트림에 기록된다. invokeExact()throw로 선언 되었기 때문에 메서드 헤더에 Throwable추가 throws Throwable했습니다 main().

Q : 이전 답변에서 조회 개체는 호출 사이트에 액세스 할 수있는 대상에만 액세스 할 수 있다고 언급했습니다. 액세스 할 수없는 대상에 대한 메서드 핸들을 얻으려는 시도를 보여주는 예제를 제공 할 수 있습니까?

A : 목록 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)); } }

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?

A :Math 클래스의 double pow(double a, double b)클래스 메서드 를 기반으로 예제를 만들었습니다 . 이 예제에서는 메서드에 대한 메서드 핸들을 가져 pow()와서 전달 된 두 번째 인수 pow()가 always가 되도록이 메서드 핸들을 변환합니다 10. 목록 4를 확인하십시오.