개요
자바 8에서 람다 표현식은 기존의 익명 클래스 방식과 다르게
1. 익명 클래스를 생성하지 않고
2. 바이트코드 수준에서 최적화된다.
그럼에도 불구하고 람다를 배우면 항상 그냥 순회하는게 성능이 더 좋아~ 오버헤드가 발생해~ 로만 대강 알고있던 성능저하가 존재한다고 알고있었다.
그럼에도 불구하고 개발자들이 람다라는 표현식의 성능 최적화를 위해 어떻게 이를 구현했는지 바이트코드 분석을 통해 알아보자.
요약
디슈거링(Desugaring)이 수행되며, 람다 표현식은 일반적인 정적 메서드로 변환된다.
이후, 실행 시점에서 `invokedynamic` 바이트코드를 활용하여 동적으로 `MethodHandle`을 참조하게 된다.
디슈거링(Desugaring) 개념
자바 8에서 람다 표현식은 내부적으로 익명 클래스를 생성하지 않고, 대신 바이트코드에서 메서드 참조를 통해 최적화된다.
이 과정에서 수행되는 것이 디슈거링(desugaring)인데, 이는 컴파일러가 람다 표현식을 일반 메서드로 변환하는 과정을 의미한다.
즉, 람다 표현식은 컴파일 시점에 익명 클래스로 변환되는 것이 아니라 일반적인 정적 메서드나 인스턴스 메서드로 변환되며, 실제 실행될 때는 invokedynamic 바이트코드를 활용하여 메서드 핸들을 참조하게 된다.
요약
- 자바 8 이전에는 람다 대신 익명 클래스를 사용해야 했음.
- 자바 8 이후 람다는 컴파일러에 의해 일반 메서드로 변환됨.
- 실제 실행 시점에는 `invokedynamic`을 활용하여 `MethodHandle`을 참조
람다 표현식의 디슈거링 예제
위의 람다 표현식 (s -> s.length()) 은 컴파일 후 어떻게 변환될까?
이를 이해하기 위해, 컴파일 후Javap 도구를 이용해 바이트코드를 분석해보자.
컴파일 후 바이트코드 분석 (`javap` 활용)
`invokedynamic`와 `LambdaMetafactory`
람다 표현식이 사용된 곳에서 invokedynamic 바이트코드가 사용된 것을 확인할 수 있다.
자바 8에서는 invokedynamic을 통해 동적으로 메서드를 찾아 실행할 수 있으며, 람다 표현식의 메서드 핸들을 생성하는 역할을 LambdaMetafactory가 담당한다.
📌 컴파일러는 다음과 같은 변환을 수행한다.
1. LambdaDesugaringExample 클래스에 새로운 정적 메서드를 생성한다.
private static Integer lambda$s$0(String s) {
return s.length();
}
2. invokedynamic을 사용하여 LambdaMetafactory에서 메서드 핸들을 생성하고 이를 Function<String, Integer> 타입으로 반환한다.


MethodHandle이 활용되는 이유
- 런타임 성능 최적화 가능
- MethodHandle을 사용하면 JVM이 실행 중에 동적으로 최적화 (Inlining)할 수 있다.
- 기존의 익명 클래스 방식보다 더 효율적인 코드가 생성된다.
- 람다를 일반 메서드처럼 표현 가능
- 람다는 내부적으로 MethodHandle을 통해 정적 메서드처럼 다룰 수 있어 JVM이 쉽게 인라인(inline)할 수 있다.
- 불필요한 익명 클래스 생성을 피함
- 기존에는 익명 클래스를 사용했으나, invokedynamic을 사용하면 불필요한 클래스 파일이 생성되지 않아 클래스 로딩 비용이 감소한다.
정리
자바 8에서 람다는 단순한 문법적 변화가 아니라 JVM 수준에서 최적화된 방식으로 동작한다.
- 람다 표현식은 컴파일 시점에 일반 메서드로 변환된다 (디슈거링).
- 컴파일된 바이트코드는 invokedynamic을 사용하여 MethodHandle을 참조한다.
- 실행 시점에 LambdaMetafactory를 통해 람다 메서드를 연결하고 최적화된 방식으로 실행된다.
이러한 방식 덕분에 기존 익명 클래스 방식보다 가벼우면서도 더 빠른 성능을 제공할 수 있다.
문제 1: 바이트 코드 수준에서 함수 호출을 어떻게 표현할 것인가?
🔹 기존 방식
- 기존 자바에서는 메서드를 직접 참조할 방법이 없었음.
- 익명 클래스 또는 인터페이스 구현을 통해 메서드를 전달해야 했음.
✅ 해결 방법
- invokedynamic + LambdaMetafactory를 사용하여 동적 메서드 핸들링을 수행.
- 컴파일러가 람다 표현식을 일반 메서드로 변환(디슈거링) 하고, 바이트코드에서 invokedynamic을 사용해 동적으로 메서드 핸들을 참조하도록 변경.
문제 2: 함수 타입 변수의 인스턴스는 어떻게 만들 것인가?
🔹 기존 방식
- Comparator, Runnable 같은 인터페이스를 익명 클래스로 구현하여 객체를 생성.
✅ 해결 방법
- 람다는 Functional Interface의 인스턴스로 변환되도록 설계.
- 기존의 Comparator, Runnable, Callable 같은 단일 메서드 인터페이스를 활용하여 람다를 쉽게 적용.
- 기존 API와 호환 가능하도록 유지.
문제 3: 변성(Variance) 처리는 어떻게 할 것인가?
🔹 기존 방식
- 익명 클래스 기반의 방식에서는 타입 매개변수(Generics)의 변성이 명확했음.
- 람다 표현식을 도입하면, 해당 타입 정보를 정확하게 유지할 필요가 있음.
✅ 해결 방법
- 컴파일러가 Functional Interface의 타입 정보를 유지하면서, 람다를 특정 타입으로 변환.
- 타입 시스템을 복잡하게 만들지 않기 위해 람다를 항상 Functional Interface의 인스턴스로 변환하도록 설계.
문제 4: 새로운 Function Type을 추가하면 어떤 문제가 발생할까?
🔹 기존 방식
- Callable<T>, Runnable 같은 인터페이스를 사용했지만, 새로운 함수 타입을 추가하려면 새로운 바이트코드 및 시그니처가 필요함.
❌ 대안 검토 (실패)
- 새로운 Function Type을 도입하면, 새로운 바이트코드 규칙과 검증 규칙이 필요해짐.
- 구형/신형 라이브러리 간의 호환성이 깨짐.
- 런타임에서 복잡한 타입 추론 문제 발생.
✅ 해결 방법
- 새로운 Function Type을 추가하는 대신, 기존의 인터페이스 기반 Functional Interface를 재활용.
- 타입 시스템을 변경하지 않고, 기존의 제네릭 기반 구조를 유지하면서 람다 적용.
문제 5: JVM에서 람다 표현식을 효율적으로 실행할 방법?
🔹 기존 방식
- 익명 클래스 기반 방식은 클래스 로딩 비용 증가.
- 불필요한 객체 생성과 메모리 사용 증가.
✅ 해결 방법
- 람다를 MethodHandle을 활용하여 실행할 수 있도록 invokedynamic 사용.
- 실행 시점에 LambdaMetafactory를 통해 최적의 구현을 동적으로 결정.
- JVM이 필요할 때만 인스턴스를 생성하여 메모리 사용 최적화.
출처 : https://cr.openjdk.org/~briangoetz/lambda/lambda-translation.html
Translation of Lambda Expressions
Translation of Lambda Expressions April 2012 <!-- This document is in Markdown format: http://daringfireball.net/projects/markdown/ [title]: Translation of Lambda Expressions Changes from version dated January 2012 - removed support for recursive lambdas a
cr.openjdk.org
'Java' 카테고리의 다른 글
해시 충돌과 참조 지역성: 자바 HashMap 성능 저하 테스트 (0) | 2025.02.19 |
---|---|
자바8 람다와 함수형 인터페이스 [2] : @FunctionalInterface 구현체 작성법(람다 표현식) (0) | 2025.02.11 |
자바8 람다와 함수형 인터페이스 [1] : 람다의 등장 배경 (0) | 2025.02.11 |
JAVA 문법 QUIZ [2] (2) | 2025.02.04 |
Java 문법 QUIZ [1] (0) | 2025.02.04 |