JVM에 추가되는 모든 새로운 멋진 기능에 대해 계속 듣고 있으며 이러한 멋진 기능 중 하나는 invokedynamic입니다. 나는 그것이 무엇인지 알고 싶습니다. 어떻게 Java에서 반사 프로그래밍을 더 쉽게 또는 더 좋게합니까?
답변
이것은 새로운 JVM 명령어로, 컴파일러가 이전보다 더 느슨한 사양으로 메소드를 호출하는 코드를 생성 할 수있게합니다. ” 덕 타이핑 “이 무엇인지 알고 있다면 invokedynamic은 기본적으로 오리 타이핑을 허용합니다. 자바 프로그래머가 할 수있는 것만 큼 많지는 않다. 툴 제작자라면이를 사용하여보다 유연하고 효율적인 JVM 기반 언어를 구축 할 수 있습니다. 여기 에 많은 세부 사항을 제공하는 정말 달콤한 블로그 게시물이 있습니다.
답변
얼마 전에 C #은 C # 내에 멋진 기능, 동적 구문을 추가했습니다.
Object obj = ...; // no static type available
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.
반사 메소드 호출의 구문 설탕으로 생각하십시오. 매우 흥미로운 응용 프로그램을 가질 수 있습니다. 참조 http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter를
C #의 동적 유형을 담당하는 Neal Gafter는 SUN에서 MS로 결함이 있습니다. 따라서 같은 일이 SUN 내부에서 논의되었다고 생각하는 것은 무리가 없습니다.
얼마 지나지 않아 일부 Java 친구가 비슷한 것을 발표했습니다.
InvokeDynamic duck = obj;
duck.quack();
불행히도이 기능은 Java 7에서 찾을 수있는 곳이 아닙니다. 매우 실망했습니다. Java 프로그래머에게는 프로그램에서 쉽게 활용할 수있는 방법이 없습니다 invokedynamic
.
답변
invokedynamic을 계속하기 전에 이해해야 할 두 가지 개념이 있습니다.
1. 정적 대 다이너 민 입력
정적 -컴파일 타임에 형식 검사를 수행합니다 (예 : Java)
동적 -런타임시 형식 검사를 수행합니다 (예 : JavaScript)
형식 검사는 프로그램이 형식에 안전한지 확인하는 프로세스입니다. 즉, 클래스 및 인스턴스 변수, 메서드 매개 변수, 반환 값 및 기타 변수에 대한 형식화 된 정보를 검사합니다. 예를 들어 Java는 컴파일 타임에 int, String, ..에 대해 알고 있지만 JavaScript의 객체 유형은 런타임에만 결정할 수 있습니다.
2. 강한 대 약한 타이핑
강력 함 -조작에 제공된 값 유형에 대한 제한 사항을 지정합니다 (예 : Java).
약함 -조작의 인수가 호환되지 않는 유형 (예 : Visual Basic) 인 경우 조작의 인수를 변환 (캐스트)합니다.
Java가 정적이고 약한 유형임을 알고 JVM에서 동적 및 강력 유형 언어를 어떻게 구현합니까?
invokedynamic은 프로그램이 컴파일 된 후 가장 적합한 메소드 또는 함수 구현을 선택할 수있는 런타임 시스템을 구현합니다.
예 :
(a + b)를 가지고 컴파일 타임에 변수 a, b에 대해 아는 것은 invokedynamic이 런타임에 Java에서 가장 적합한 메소드에이 조작을 맵핑합니다. 예를 들어, a, b가 문자열 인 경우 method (String a, String b)를 호출하십시오. a, b가 int 인 경우 method (int a, int b)를 호출하십시오.
invokedynamic은 Java 7에서 도입되었습니다.
답변
Java Records 기사의 일부로 Inoke Dynamic의 동기에 대해 설명했습니다. Indy의 대략적인 정의부터 시작하겠습니다.
인디 소개
Invoke Dynamic ( Indy 라고도 함 )은 동적 유형 언어에 대한 JVM 지원을 향상시키기 위해 JSR 292의 일부였습니다 . Java 7에서 첫 번째 릴리스 이후, 수하물 invokedynamic
과 함께 opcode java.lang.invoke
는 JRuby와 같은 동적 JVM 기반 언어에서 상당히 광범위하게 사용됩니다.
인디는 동적 언어 지원을 향상시키기 위해 특별히 설계되었지만 그 이상을 제공합니다. 사실, 언어 디자이너가 다이나믹 타입 곡예부터 다이나믹 전략에 이르기까지 모든 형태의 다이나믹이 필요한 곳에 사용하는 것이 적합합니다!
예를 들어, Java가 정적으로 유형이 지정되었지만 Java 8 Lambda Expressions는 실제로를 사용하여 구현 invokedynamic
됩니다!
사용자 정의 가능 바이트 코드
꽤 오랫동안 JVM은 invokestatic
정적 메소드 invokeinterface
호출, 인터페이스 메소드 invokespecial
호출, 생성자 super()
또는 개인 메소드 invokevirtual
호출 및 인스턴스 메소드 호출의 네 가지 메소드 호출 유형을 지원했습니다 .
이들의 차이점에도 불구하고, 이러한 호출 유형은 하나의 공통된 특성을 공유 합니다 . 자체 논리로이를 강화할 수는 없습니다 . 반대로, invokedynamic
원하는 방식으로 호출 프로세스를 부트 스트랩 할 수 있습니다. 그런 다음 JVM은 Bootstrapped Methods를 직접 호출합니다.
인디는 어떻게 작동합니까?
JVM이 invokedynamic
명령어를 처음 볼 때 Bootstrap Method 라는 특수 정적 메소드를 호출 합니다 . 부트 스트랩 메소드는 호출 할 실제 로직을 준비하기 위해 작성한 Java 코드입니다.
그런 다음 부트 스트랩 메소드는의 인스턴스를 리턴합니다 java.lang.invoke.CallSite
. 이것은 CallSite
실제 방법에 대한 참조를 보유합니다 MethodHandle
.
이제부터 JVM이이 invokedynamic
명령을 다시 볼 때마다 느린 경로를 건너 뛰고 기본 실행 파일을 직접 호출합니다. 변경 사항이 없으면 JVM은 느린 경로를 계속 건너 뜁니다.
예 : Java 14 레코드
Java 14 Records
는 벙어리 데이터 홀더로 간주되는 클래스를 선언하는 멋진 간단한 구문을 제공합니다.
이 간단한 기록을 고려하면 :
public record Range(int min, int max) {}
이 예제의 바이트 코드는 다음과 같습니다.
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
그것에서 부트 스트랩 방법 표 :
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
따라서 클래스에 상주하는 레코드 의 부트 스트랩 메소드가 호출 됩니다. 보다시피,이 부트 스트랩 방법에는 다음과 같은 매개 변수가 필요합니다.bootstrap
java.lang.runtime.ObjectMethods
MethodHandles.Lookup
조회 컨텍스트 를 나타내는 인스턴스 (Ljava/lang/invoke/MethodHandles$Lookup
부분).- 메소드 이름 (예
toString
,equals
,hashCode
, 등) 부트 스트랩은 링크 것입니다. 예를 들어, 값이 인toString
경우 부트 스트랩은 이 특정 레코드 의 실제 구현을 가리키는ConstantCallSite
(aCallSite
는 절대 변경되지 않음)을 반환합니다toString
. TypeDescriptor
방법 (용Ljava/lang/invoke/TypeDescriptor
부분).- 유형 토큰, 즉
Class<?>
Record 클래스 유형을 나타냅니다. 그것은이다
Class<Range>
이 경우. - 세미콜론으로 구분 된 모든 구성 요소 이름 목록입니다 (예 🙂
min;max
. MethodHandle
구성 요소 당 하나 . 이 방법으로 부트 스트랩 메소드는MethodHandle
이 특정 메소드 구현을위한 컴포넌트를 기반으로 작성할 수 있습니다 .
invokedynamic
명령은 부트 스트랩 방법에 대한 모든 인수를 전달합니다. 부트 스트랩 메소드는의 인스턴스를 반환합니다 ConstantCallSite
. 이것은 ConstantCallSite
예를 들어, 요구 된 방법의 구현에 대한 참조를 보유한다 toString
.
왜 인디?
Reflection API와 달리 java.lang.invoke
JVM은 모든 호출을 완전히 볼 수 있기 때문에 API가 매우 효율적입니다. 따라서 가능한 한 느린 경로를 피하는 한 JVM은 모든 종류의 최적화를 적용 할 수 있습니다!
효율성 주장 외에도 invokedynamic
접근 방식은 단순성으로 인해보다 안정적이고 덜 취약 합니다.
또한 Java 레코드에 대해 생성 된 바이트 코드는 특성 수와 무관합니다. 따라서 적은 바이트 코드와 빠른 시작 시간.
마지막으로, 새 버전의 Java에 새롭고보다 효율적인 부트 스트랩 메소드 구현이 포함되어 있다고 가정합니다. 을 사용하면 invokedynamic
앱이 재 컴파일없이이 개선 사항을 활용할 수 있습니다. 이런 식으로 우리는 일종의 정방향 바이너리 호환성을 갖습니다 . 또한 이것이 우리가 이야기 한 역동적 인 전략입니다!
다른 예
Java Records 외에도 invoke dynamic 은 다음과 같은 기능을 구현하는 데 사용되었습니다.
- Java 8 이상의 Lambda 표현식 :
LambdaMetafactory
- Java 9+에서의 문자열 연결 :
StringConcatFactory