Project Valhalla 설명: 어떻게 JDK 28에 마침내 도입되는가
요약
Project Valhalla가 JDK 28에서 프리뷰 기능으로 도입되며, Java의 메모리 효율성을 혁신적으로 개선합니다. 기본 클래스(primitive classes) 도입을 통해 객체의 힙 할당 부담을 줄이고 C++나 Rust 수준의 성능을 목표로 합니다.
핵심 포인트
- JDK 28에서 기본 클래스(primitive classes) 프리뷰 기능 출시
- 포인터 간접 참조 제거로 메모리 레이아웃 최적화 및 성능 향상
- 데이터 집약적 워크로드에서 2~5배의 메모리 감소 효과 기대
- 기존 코드의 하위 호환성을 유지하는 선택적(opt-in) 마이그레이션 방식
Project Valhalla 설명: 어떻게 JDK 28에 마침내 도입되는가
Meta Description: Project Valhalla 설명 — 가치 타입 (value types), 기본 클래스 (primitive classes)와 함께 10년간의 JVM 연구가 JDK 28에 어떻게 도착하는지, 그리고 이것이 여러분의 Java 코드에 무엇을 의미하는지 알아보세요.
TL;DR: Project Valhalla는 수십 년 만에 이루어지는 Java의 가장 야심 찬 JVM 개편입니다. 거의 10년의 개발 끝에, 핵심 기능인 기본 클래스 (primitive classes, 이전 명칭 "value types") 및 null 제한 타입 (null-restricted types)이 JDK 28에서 프리뷰 기능 (preview features)으로 출시됩니다. 그 결과: 하위 호환성을 깨뜨리지 않으면서도, 데이터 집약적인 워크로드에서 C++ 및 Rust에 필적할 수 있는 극적인 메모리 효율성 향상, 더 평탄한 데이터 레이아웃 (flatter data layouts), 그리고 성능 이득을 얻을 수 있습니다.
핵심 요약 (Key Takeaways)
- JDK 28은 Valhalla의 기본 클래스 (primitive classes)에 대한 첫 번째 프로덕션 프리뷰를 제공하며, 수년간의 인큐베이션 (incubation) 기간을 마칩니다.
- 기본 클래스는 "포인터 세금 (pointer tax)"을 제거합니다 — 객체가 더 이상 항상 힙 (heap) 할당을 필요로 하지 않습니다.
- 가치 객체 (Value objects) 및 null 제한 타입 (null-restricted types)은 개발자에게 메모리 레이아웃 (memory layout)에 대한 세밀한 제어권을 부여합니다.
- 기본 타입에 대한 제네릭 (Generics over primitives, 예:
List<int>)은 이후 JDK 릴리스에서 제공될 예정입니다. - 마이그레이션은 주로 선택 사항 (opt-in) 입니다 — 기존 코드는 깨지지 않습니다.
- 성능 벤치마크에 따르면 초기 테스트에서 숫자 중심의 데이터 구조에 대해 2~5배의 메모리 감소를 보여줍니다.
- 이는 2004년 Java 5가 제네릭 (generics)을 도입한 이후 JVM 타입 시스템 (type system)에 가해진 가장 중대한 변화입니다.
Project Valhalla란 무엇인가?
만약 당신이 몇 년 이상 전문적으로 Java를 작성해 왔다면, 아마도 이 언어의 가장 지속적인 좌절 요소에 부딪혀 보았을 것입니다: 모든 것은 객체이며, 모든 객체는 힙 (heap)에 존재한다는 점입니다. 정수 리스트가 필요한가요? Java는 각 int를 Integer 객체로 감싸고, 이를 힙에 할당하며, 접근할 때마다 포인터 (pointer)를 추적합니다. 백만 개의 숫자가 담긴 리스트의 경우, 이는 백만 개의 개별 힙 할당을 의미하며 — 이는 캐시 미스 (cache misses)와 GC 압박 (GC pressure)이 발생할 백만 번의 기회를 의미합니다.
Project Valhalla는 Oracle과 OpenJDK 커뮤니티에 의해 2014년경 시작되었으며, 이 문제를 언어 및 JVM 수준에서 해결하기 위해 존재합니다. 프로젝트 리드인 Brian Goetz가 만든 이 프로젝트의 핵심 슬로건은 모든 것을 말해줍니다: "클래스처럼 코딩하고, int처럼 작동한다."
이 아이디어는 개발자들에게 새로운 종류의 타입을 제공하는 것입니다. 이 타입은 Java 클래스의 사용 편의성(필드, 메서드, 제네릭)을 가지면서도, 원시 타입(primitive)의 런타임 동작(스택 할당 가능, 인라인 가능, 식별자 없음, 포인터 간접 참조 없음)을 가집니다. 거의 10년간의 설계 반복, JEP 검토, 프로토타입 구축 끝에, 이 아이디어가 JDK 28에서 프리뷰 기능으로 구현되고 있습니다.
[INTERNAL_LINK: Java 성능 최적화 모범 사례]
간략한 역사: 왜 이렇게 오래 걸렸을까?
소프트웨어 분야에서 10년은 긴 시간입니다. Valhalla가 왜 그렇게 오래 걸렸는지 이해하려면, 팀이 어떤 제약 조건 하에서 작업했는지를 알아야 합니다:
하위 호환성 문제 (The Backward Compatibility Problem)
Java의 가장 큰 강점은 동시에 가장 큰 제약 조건이기도 합니다. 2001년에 작성된 코드가 여전히 2026년 JVM에서 실행됩니다. 제네릭, 리플렉션, 직렬화, 동기화 및 전체 기존 생태계와 상호 작용하는 새로운 타입 범주를 아무것도 깨뜨리지 않고 도입하는 것은 정말 어려운 일입니다. 초기 Valhalla 프로토타입(‘LWorld’ 및 ‘Valhalla 1’ 실험 빌드)은 재설계를 필요로 하는 수십 가지의 미묘한 상호작용을 드러냈습니다.
제네릭 문제 (The Generics Problem)
Java의 제네릭은 타입 소거(type erasure)를 통해 구현됩니다. List<String>과 List<Integer>는 런타임에 동일한 클래스입니다. 이것은 참조 타입(reference types)에는 잘 작동하지만, 메모리 레이아웃이 다른 값 타입(value types)에게는 근본적으로 문제가 됩니다.
이 기능은 최소 세 번의 주요 설계 개편(design overhaul)을 거쳤습니다. 처음에는 "값 타입 (value types)"으로 시작했으나, 이후 "인라인 클래스 (inline classes)"가 되었고, 그다음에는 "원시 클래스 (primitive classes)"와 "값 객체 (value objects)"의 조합으로 발전했습니다. 각 전환점은 프로토타입 사용자들의 실제 피드백과 엣지 케이스 (edge cases)에 대한 심층적인 분석을 통해 이루어졌습니다. 미완성된 결과물을 출시하지 않은 팀의 노력은 높게 평가받아야 마땅합니다.
JDK 28에 실제로 포함된 내용: 핵심 기능
원시 클래스 (Primitive Classes)
가장 핵심적인 기능입니다. **원시 클래스 (primitive class)**는 primitive 키워드로 선언되며 순수한 값을 나타냅니다. 이는 정체성(identity)이 없으며, null 제한 컨텍스트 (null-restricted contexts)에서 기본적으로 null이 될 수 없습니다. 또한 JVM은 이 클래스의 표현을 스택 (stack), 배열 (arrays) 또는 포함하는 객체 (containing objects) 내로 평탄화 (flatten)할 수 있는 자유를 가집니다.
primitive class Point {
double x;
double y;
...
이 기능이 강력한 이유: 백만 개의 요소를 가진 Point[] 배열은 이제 메모리에 백만 개의 힙 할당 (heap-allocated) 객체를 가리키는 백만 개의 포인터가 아니라, 이백만 개의 연속된 double 값으로 저장됩니다. 캐시 지역성 (cache locality)이 극적으로 향상되며, 가비지 컬렉션 (GC)이 처리해야 할 작업도 훨씬 줄어듭니다.
값 객체 (Value Objects)
모든 클래스가 원시 클래스가 될 수 있거나 되어야 하는 것은 아닙니다. **값 객체 (value objects)**는 조금 더 완화된 버전입니다. 정체성이 없지만 (참조에 의한 의미 있는 == 연산 불가, synchronized 지원 불가), 여전히 null이 될 수 있으며 일반 객체처럼 힙 (heap)에 존재할 수 있습니다. 이를 "정체성이 없는 참조 타입 (identity-free reference types)"라고 생각하면 됩니다.
value class ImmutableRange {
int low;
int high;
...
값 객체는 DTO, 동작이 포함된 레코드 (records-with-behavior), 그리고 원시 시맨틱 (primitive semantics)을 완전히 채택하지 않으면서도 JVM이 레이아웃을 최적화할 수 있기를 원하는 불변 도메인 객체 (immutable domain objects)에 매우 적합합니다.
null 제한 타입 (Null-Restricted Types)
JDK 28은 또한 ! 어노테이션을 사용하는 **null 제한 타입 구문 (null-restricted type syntax)**을 도입합니다.
Point! origin = new Point(0, 0); // null이 될 수 없음
Point nullable = null; // 여전히 허용됨
이는 기본 클래스 (primitive classes, 기본 형태에서는 기본적으로 null이 될 수 없음)와 자연스럽게 짝을 이루며, 개발자들에게 API에서 비-null성 (non-nullability)을 표현할 수 있는 방법을 제공합니다. 이는 Java 개발자들이 JVM이 무시하는 @NonNull 어노테이션 (annotations)에 의존하지 않고 수년간 원해왔던 기능입니다.
[INTERNAL_LINK: Java records and sealed classes guide]
성능 영향: 수치가 말해주는 것
JDK 28 얼리 액세스 (early-access) 빌드를 실행하는 OpenJDK 엔지니어와 커뮤니티 구성원들의 초기 벤치마크 (benchmarks) 결과는 고무적인 모습을 보여줍니다:
| 시나리오 | 전통적인 Java | JDK 28 (기본 클래스) | 개선 사항 |
|---|---|---|---|
Point[] 배열, 100만 개 요소 | ~24 MB 힙 (heap) | ~16 MB 힙 (heap) | ~33% 감소 |
| ... | |||
| 참고: 이 수치는 얼리 액세스 테스트 결과이며 워크로드 (workload)에 따라 크게 달라질 수 있습니다. 실제 운영 환경의 결과는 다를 수 있습니다. |
이러한 이점은 데이터 집약적 워크로드 (data-intensive workloads), 즉 과학 계산 (scientific computing), 금융 모델링 (financial modeling), 게임 개발 (game development), 그래픽 처리 (graphics processing), 그리고 머신러닝 추론 (machine learning inference)에서 가장 극적으로 나타납니다. 만약 귀하의 애플리케이션이 주로 문자열 (Strings)과 데이터베이스 행 (database rows)을 셔플링 (shuffles)하는 작업 위주라면, 개선 효과는 더 완만할 것입니다.
이것이 실제 Java 개발에 의미하는 바
라이브러리 제작자를 위한 의미
만약 귀하가 라이브러리를 유지 관리하고 있다면 — 특히 수치 계산 (numerics), 컬렉션 (collections), 또는 데이터 처리 (data processing) 분야라면 — JDK 28 프리뷰 기능 (preview features)을 지금 바로 검토해 볼 가치가 있습니다. Eclipse Collections 및 Apache Commons Math와 같은 라이브러리들은 핵심 데이터 구조에 기본 클래스 (primitive classes)를 채택함으로써 상당한 이득을 얻을 수 있습니다.
주의할 점: 프리뷰 기능은 컴파일 및 런타임 (runtime) 시 --enable-preview 옵션이 필요하므로, 이를 사용하는 라이브러리를 배포한다는 것은 사용자 또한 해당 옵션을 선택(opt in)해야 함을 의미합니다. 대부분의 라이브러리 제작자들은 이 기능이 정식 확정될 때까지(아마도 JDK 29 또는 30) 기다릴 것입니다.
애플리케이션 개발자를 위한 의미
만약 JDK 28 기반으로 새로운 서비스를 구축하고 있다면, 다음과 같은 경우에 기본 클래스 (primitive classes) 사용을 고려하십시오:
- 좌표 및 기하학 타입 (Coordinate and geometry types) (
Point,Vector,Matrix) - 금융 기본 타입 (Financial primitives) (
Money,Percentage,Rate) java.time이 이미 제공하는 것 이상의 시간/날짜 값 타입 (Time/date value types)- 렌더링 코드에서의 색상 및 픽셀 타입 (Color and pixel types)
기존 코드베이스의 경우, 마이그레이션 경로는 완만합니다. record 또는 단순 불변 클래스 (immutable class)를 기본 클래스 (primitive class) 또는 값 클래스 (value class)로 점진적으로 변환할 수 있으며, 컴파일러가 호환되지 않는 부분을 알려줄 것입니다 (보통 인스턴스에 synchronized를 사용하거나 참조 동등성 (reference equality)에 의존하는 코드의 경우).
프레임워크 및 도구 공급업체를 위한 사항
Spring, Hibernate, Micronaut와 같은 프레임워크는 값 객체 (value objects)와 기본 클래스 (primitive classes)를 올바르게 처리하기 위해 업데이트가 필요합니다. 특히 리플렉션 (reflection), 직렬화 (serialization) (Jackson, Gson), 그리고 영속성 매핑 (persistence mapping)과 관련된 부분입니다. Spring 팀은 이미 초기 호환성 작업을 시작했습니다. JDK 28의 GA (General Availability) 출시 후 6~12개월 이내에 주요 프레임워크에서 완전한 지원이 이루어질 것으로 예상됩니다.
주시할 만한 도구들:
- IntelliJ IDEA는 이미 2026.1 빌드에서 초기 Valhalla 검사 (inspection) 지원을 제공합니다 — JDK 28 프리뷰 기능을 실험하기 위한 최고의 IDE 경험을 제공합니다.
- JProfiler는 기본 클래스를 인식하는 힙 분석 (heap analysis) 기능을 추가하고 있으며, 이는 메모리 절감 효과를 검증하는 데 필수적일 것입니다.
- Gradle 9.x는 적절한 툴체인 (toolchain) 구성과 함께
--enable-preview플래그를 기본적으로 지원합니다.
JDK 28에 포함되지 않은 것 (아직)
Valhalla의 JDK 28 배포에 포함되지 않는 사항에 대해 솔직하게 밝히는 것이 중요합니다:
- 제네릭 특수화 (Generic specialization) (
List<int>,Optional<double>) — 이는 다음 주요 단계이며, JDK 29–30을 목표로 할 가능성이 높습니다. - 유니버설 제네릭 (Universal generics) — 참조 타입과 기본 타입 모두에서 균일하게 작동하는 제네릭의 완전한 비전입니다.
- 모든 컬렉션 API에서의 기본 클래스 배열 (Primitive class arrays in all collection APIs) — 표준 라이브러리 업데이트는 특수화 (specialization) 이후에 뒤따를 것입니다.
JDK 28 릴리스는 완성된 건물이 아니라 토대(foundation)입니다. 가장 혁신적인 유스케이스(use cases) — 예를 들어 ArrayList<int>가 박싱(boxing)을 완전히 피할 수 있도록 ArrayList를 재작성하는 것 — 은 현재 진행 중인 특수화 (specialization) 작업이 필요합니다.
[INTERNAL_LINK: Java generics deep dive]
오늘 바로 시도하는 방법
JDK 28 얼리 액세스 (early-access) 빌드는 jdk.java.net에서 사용할 수 있습니다. Valhalla 기능을 실험하려면 다음을 수행하세요:
# 프리뷰 기능(preview features)을 활성화하여 컴파일
javac --enable-preview --release 28 MyClass.java
...
실험을 위한 권장 설정:
- IntelliJ IDEA 2026.1+ 버전을 사용하고, JDK 28 EA를 프로젝트 SDK로 구성하세요.
- 프로젝트 구조(Project Structure)에서 언어 수준(language level)을 "28 (Preview)"로 설정하세요.
- 작고 독립적인 모듈로 시작하세요 — 전체 코드베이스에 프리뷰 기능을 활성화하지 마세요.
- JMH (Java Microbenchmark Harness)를 사용하여 벤치마크를 작성하고, 특정 워크로드에서의 실제 이득을 측정하세요.
Valhalla vs. 다른 언어의 접근 방식
Java의 접근 방식은 다른 언어들이 이 문제를 처리하는 방식과 어떻게 비교될까요?
| 언어 | 접근 방식 | 트레이드오프 (Tradeoffs) |
|---|---|---|
| Java (Valhalla) | 원시 클래스 (Primitive classes) + 값 객체 (value objects) | 하위 호환성 유지, 점진적 마이그레이션, JVM 레벨 최적화 |
| ... |
Java의 접근 방식은 기존의 거대한 생태계에 값 의미론 (value semantics)을 소급 적용(retrofit)한다는 점에서 독보적으로 야심 차 있습니다. 이는 처음부터 이를 설계한 방식(Rust나 Swift가 한 방식)보다 더 어렵지만, 기존의 수십억 줄에 달하는 Java 코드를 다시 작성하지 않고도 혜택을 볼 수 있음을 의미합니다.
자주 묻는 질문 (FAQ)
Q: JDK 28로 업그레이드하면 기존 Java 코드가 깨지나요?
아니요. JDK 28의 Valhalla 기능은 선택적 프리뷰 기능 (opt-in preview features)입니다. 기존 코드는 이전과 똑같이 컴파일되고 실행됩니다. 클래스를 primitive 또는 value로 명시적으로 선언하거나, null 제한 타입 구문 (null-restricted type syntax)을 사용할 때만 새로운 동작을 접하게 됩니다.
Q: primitive class는 record와 동일한가요?
그들은 서로 연관되어 있지만 다릅니다. record는 불변 데이터 캐리어 (immutable data carriers)를 위한 간결한 구문이지만, 여전히 정체성 (identity)을 가진 일반적인 참조 타입 (reference type)입니다. primitive class는 여기서 더 나아갑니다. 정체성이 없으며, 잠재적인 스택/인라인 할당 (stack/inline allocation)을 지원하고, JVM이 이를 배열이나 다른 객체 내에 플래튼 (flatten) 할 수 있습니다. primitive class는 JVM이 int처럼 취급할 수 있는 record라고 생각하면 됩니다.
Q: Valhalla 기능들은 언제 최종 확정(non-preview)되나요?
현재 JEP 로드맵과 OpenJDK 메일링 리스트 논의를 바탕으로 할 때, 핵심적인 primitive class 및 값 객체 (value object) 기능은 JDK 29 또는 JDK 30 (2026년 말에서 2027년 중반)에 최종 확정되는 것을 목표로 하고 있습니다. 제네릭 특수화 (Generic specialization)는 JDK 30–31에서 뒤따를 가능성이 높습니다.
Q: JDK 28의 프로덕션 코드에서 primitive class를 사용해야 할까요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기