지난글에서 람다의 내부/외부 표현에 대한 글을 작성했습니다.
이번에는 람다의 내부적인 동작에 대한 상세한 이해를 위해 JVM 관련 부분과 bytecode 위주로 언급합니다.
JVM의 opcode
compile된 bytecode를 보면 네종류의 opcode를 사용하여 Java의 method를 표현합니다.
1.7 이전
invokestatic: static method 실행
invokevirtual: instance method 실행
invokeinterface: interface method 실행
invokespecial: exact한 함수 수행 - override 불가, 더이상 변화가 없는 함수들
생성자, private method, super call
invokedynamic
동적 타입 언어를 위한 opcode
Jruby, Jython, Groovy 같은 JVM에서 돌아가는 동적 타입언어를 지원하기 위해 추가.
Java8 부터 default method, lambda compile시에 사용
1.7 이전에는 네종류의 opcode를사용했으나, java8 부터는 람다의 경우 invokedynamic이라는 opcode를 사용합니다. invokedynamic은 1.7에서 새롭게 추가되었지만, 1.7에서 javac로 compile된 bytecode에는 절대 사용 되지 않습니다.
원래의 용도가 java가 아닌 다른 동적타입 언어였기 때문이죠.
invokedynamic에 대한 코드를 보기전에 기존 코드들이 bytecode상 어떻게 변경되는지 보겠습니다.
또한 각각의 opcode의 실행 순서는 거의 유사합니다.
다만 invokedynamic만 완전히 다른 형태로 실행 됩니다.
Translate Strategy
- inner class
- method handles
- dynamic proxies
- others..
- compile시에 bytecode에서는 람다를 구현하는 객체를 생성하지 않음 -> runtime에 실제 생성을 위함하는 방법만(recipe)만 표기
- 해당 recipe는 invokedynamic instruction에 동적/정적 인수 목록으로 encoding 된다.
참고: http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html
Invokedynamic call
- indy가 호출되면 bootstrap영역의 lambdafactory.metafactory()를 수행
- lambdafactory.metafactory(): java runtime library의 표준화 method
- 어떤 방법으로 객체를 생성할지 dynamically 결정
- 클래스 생성, 재사용, proxy, wrapper class, VM전용 API사용등등 성능 향상을 위한 최적화된 방법 사용
- java.lang.invoke.CallSite 객체를 return함.
- 해당 lambda의 lambda factory
- MethodHandle을 멤버변수로 가짐
- 람다가 변환되는 함수 인터페이스의 인스턴스를 반환
- 한번만 생성되고 재 호출시 재 사용함.
- (In a number of the possible translation strategies, we need to generate new classes. For example, if we are generating a class-per-lambda (spinning inner classes at runtime instead of compile time), we generate the class the first time a given lambda factory site is called. Thereafter, future calls to that lambda factory site will re-use the class generated on the first call.)
Lambda Desugaring
또한 이런 부분은 bytecode에서도 확인할 수 있습니다.
그럼 마지막으로 #1에서 나왔던 예제의 bytecode를 다시 확인해 보겠습니다.
Runnable lambda = () -> check(this)
lambda.run();
실제 performance는 단순 inner class를 치환하는것보다 훨씬 빠르다는 것을 테스트를 통해 증명한 내용이 아래 링크에 있습니다.
동영상을 다 보기 힘드신분은 Performance example - capture cost 부분만 돌려서 보시면 됩니다.^^
참고:https://www.infoq.com/presentations/lambda-invokedynamic
'개발이야기 > Java' 카테고리의 다른 글
Java의 메모리 관리 - Weak, Soft, Phantom reference 예제 (0) | 2017.10.29 |
---|---|
Weak reference의 이해 (1) | 2017.10.22 |
람다의 내부동작 #1 (0) | 2017.09.24 |
Java 8 Comparator (0) | 2017.09.14 |
Java 8 String join (3) | 2017.09.13 |