programing

invokedynamic을 사용하여 Java 8 람다가 호출되는 이유는 무엇입니까?

nasanasas 2020. 11. 24. 08:03
반응형

invokedynamic을 사용하여 Java 8 람다가 호출되는 이유는 무엇입니까?


invokedynamic명령어는 VM이 ​​컴파일 타임에 하드와 이어링하는 대신 런타임에 메서드 참조를 결정하는 데 사용됩니다.

이는 런타임까지 정확한 메서드와 인수 유형을 알 수없는 동적 언어에 유용합니다. 그러나 Java 람다에서는 그렇지 않습니다. 잘 정의 된 인수를 사용하여 정적 메서드로 변환됩니다. 그리고이 메서드는 invokestatic.

그렇다면 invokedynamic특히 성능 저하가있을 때 람다 의 필요성은 무엇 입니까?


Lambda는를 사용하여 호출되지 않고 invokedynamic객체 표현 은를 사용하여 생성 invokedynamic되며 실제 호출은 일반 invokevirtual또는 invokeinterface.

예를 들면 :

// creates an instance of (a subclass of) Consumer 
// with invokedynamic to java.lang.invoke.LambdaMetafactory 
something(x -> System.out.println(x));   

void something(Consumer<String> consumer) {
      // invokeinterface
      consumer.accept("hello"); 
}

모든 람다는 기본 클래스 또는 인터페이스의 인스턴스가되어야합니다. 이 인스턴스에는 때때로 원래 메서드에서 캡처 한 변수의 복사본과 부모 개체에 대한 포인터가 포함됩니다. 이것은 익명 클래스로 구현 될 수 있습니다.

왜 invokedynamic

짧은 대답은 런타임에서 코드를 생성하는 것입니다.

자바 관리자는 런타임에 구현 클래스를 생성하기로 결정했습니다. 이것은를 호출하여 수행됩니다 java.lang.invoke.LambdaMetafactory.metafactory. 해당 호출에 대한 인수 (반환 유형, 인터페이스 및 캡처 된 매개 변수)가 변경 될 수 있으므로 invokedynamic.

사용하여 invokedynamic런타임에 익명 클래스를 구성하기 위해, JVM이 실행시에 클래스의 바이트 코드를 생성 할 수 있습니다. 동일한 명령문에 대한 후속 호출은 캐시 된 버전을 사용합니다. 사용하는 또 다른 이유 invokedynamic는 이미 컴파일 된 코드를 변경하지 않고도 향후 구현 전략을 변경할 수 있기 때문입니다.

취하지 않은 길

다른 옵션은 위의 코드를 다음과 같이 번역하는 것과 동일한 각 람다 인스턴스화에 대한 내부 클래스를 만드는 컴파일러입니다.

something(new Consumer() { 
    public void accept(x) {
       // call to a generated method in the base class
       ImplementingClass.this.lambda$1(x);

       // or repeating the code (awful as it would require generating accesors):
       System.out.println(x);
    }
);   

이를 위해서는 컴파일 타임에 클래스를 생성하고 런타임 중에로드해야합니다. jvm이 작동하는 방식은 해당 클래스가 원래 클래스와 동일한 디렉토리에 있습니다. 그리고 해당 람다를 사용하는 문을 처음 실행하면 해당 익명 클래스를로드하고 초기화해야합니다.

성능에 대해

에 대한 첫 번째 호출 invokedynamic은 익명 클래스 생성을 트리거합니다. 그런 다음 opcode invokedynamic익명 인스턴스화를 수동으로 작성하는 것과 성능면에서 동등한 코드 로 대체됩니다 .


Brain Goetz는 불행히도 지금은 불가능 해 보이는 그의 논문 중 하나에서 람다 번역 전략의 이유를 설명했습니다 . 다행히도 사본을 보관했습니다.

번역 전략

내부 클래스, 메서드 핸들, 동적 프록시 등과 같이 바이트 코드에서 람다 식을 표현하는 방법에는 여러 가지가 있습니다. 이러한 접근 방식에는 각각 장단점이 있습니다. 전략을 선택할 때 두 가지 경쟁 목표가 있습니다. 특정 전략을 적용하지 않음으로써 향후 최적화를위한 유연성을 극대화하는 것과 클래스 파일 표현에 안정성을 제공하는 것입니다. JSR 292의 invokedynamic 기능을 사용하여 바이트 코드에서 람다 생성의 이진 표현을 런타임에 람다 식을 평가하는 메커니즘과 분리함으로써 이러한 목표를 모두 달성 할 수 있습니다. 람다 식을 구현하는 객체를 생성하기 위해 바이트 코드를 생성하는 대신 (예 : 내부 클래스에 대한 생성자 호출) 람다를 생성하는 방법을 설명합니다. 실제 구성을 언어 런타임에 위임합니다. 해당 레시피는 invokedynamic 명령어의 정적 및 동적 인수 목록에 인코딩됩니다.

invokedynamic을 사용하면 런타임까지 번역 전략 선택을 연기 할 수 있습니다. 런타임 구현은 람다 식을 평가하기 위해 동적으로 전략을 선택할 수 있습니다. 런타임 구현 선택은 람다 구성을위한 표준화 된 (즉, 플랫폼 사양의 일부) API 뒤에 숨겨져 있으므로 정적 컴파일러는이 API에 대한 호출을 내보낼 수 있으며 JRE 구현은 선호하는 구현 전략을 선택할 수 있습니다. invokedynamic 메카니즘을 사용하면이 늦은 바인딩 접근 방식이 부과 할 수있는 성능 비용없이이를 수행 할 수 있습니다.

컴파일러가 람다 식을 발견하면 먼저 람다 본문을 인수 목록 및 반환 형식이 람다 식의 형식과 일치하는 메서드로 내립니다 (desugars). 어휘 범위에서 캡처 된 값이있는 경우 추가 인수가있을 수 있습니다. ) 람다식이 캡처되는 지점에서 invokedynamic 호출 ​​사이트를 생성합니다.이 호출 사이트는 호출 될 때 람다가 변환되는 기능 인터페이스의 인스턴스를 반환합니다. 이 호출 사이트를 지정된 람다에 대한 람다 팩토리라고합니다. 람다 팩토리에 대한 동적 인수는 어휘 범위에서 캡처 된 값입니다. 람다 팩토리의 부트 스트랩 메서드는 람다 메타 팩토리라고하는 Java 언어 런타임 라이브러리의 표준화 된 메서드입니다.

Method references are treated the same way as lambda expressions, except that most method references do not need to be desugared into a new method; we can simply load a constant method handle for the referenced method and pass that to the metafactory.

So, the idea here seemed to be to encapsulate the translation strategy and not commit to a particular way of doing things by hiding those details. In the future when type erasure and lack of value types have been solved and maybe Java supports actual function types, they might just as well go there and change that strategy for another one without causing any problems in the users' code.


Current Java 8's lambda implementation is a compound decision:

    1. Compile the lambda expression to static method in the enclosing class; instead of compile lambdas to seperate inner class files (Scala compiles this way, so a lot $$$ class files around )
    1. Introduce a constant pool: BootstrapMethods, which wrap the static method invocation to callsite object ( can be cached for later use )

So to answer your question,

    1. current lambda implementation using invokedynamic is a little bit faster than the seperate inner class way, because no need to load these inner class files, but instead create the inner class byte[] on the fly ( to satisfy for example the Function interface ), and cached for later use.
    1. JVM team may still choose to generate seperate inner class ( by reference the enclosing class's static methods ) files, it's flexible

참고URL : https://stackoverflow.com/questions/30002380/why-are-java-8-lambdas-invoked-using-invokedynamic

반응형