본문으로 건너뛰기

© 2026 Molayo

GeekNews헤드라인2026. 06. 20. 10:35

Project Valhalla 설명: 10년 작업이 JDK 28에 도착하는 방식

요약

Java의 Project Valhalla가 JEP 401을 통해 JDK 28에 preview 기능으로 도입될 예정입니다. 객체를 클래스처럼 설계하면서도 primitive 타입처럼 효율적으로 동작하게 하여 메모리 레이아웃을 최적화하고 성능을 개선하는 것이 핵심입니다.

핵심 포인트

  • JEP 401을 통해 객체 헤더 및 힙 할당 비용을 줄이는 Value Class 도입
  • 메모리 배치를 'fluffy'에서 'dense'하게 변경하여 캐시 효율성 극대화
  • JDK 28(2027년 출시 예정)에서 --enable-preview 옵션으로 사용 가능
  • 객체의 identity와 value 차이를 고려한 새로운 코드 설계 필요성 증대

JEP 401: Value Classes and Objects이 실제 JDK preview로 들어가는 단계에 도달

  • 핵심 목표는 Java 객체를 “
    클래스처럼 코딩하고 int처럼 동작”하게 만들어 객체 헤더·힙 할당·GC·포인터 간접 참조 비용을 줄이는 것
  • JDK 28의 value class는 아직
    null 가능한 참조 타입이며, non-null 타입·전문화 제네릭·128비트 인코딩은 포함되지 않고 --enable-preview

가 필요함

  • JVM은 value object를
    스칼라화하거나 필드·배열에 힙 평탄화할 수 있지만, erased generic이나 Object

같은 상위 타입에서는 힙 객체로 materialize될 수 있음

  • Java 개발자는 identity와 value의 차이를 코드 설계에 반영해야 하며,
    ==

, synchronized

, primitive wrapper, 배열 성능, 향후 제네릭 전문화까지 영향이 이어짐

JDK 28에 들어오는 Valhalla의 범위

  • 6월 15일 Oracle 엔지니어 Lois Foltan이 JEP 401: Value Classes and Objects의 OpenJDK 메인 저장소 통합과
    JDK 28 타깃을 확인함
  • 관련 pull request는
    1,816개 파일에 걸쳐 19만 7천 줄 이상을 추가함
  • 변경 규모가 커서 통합 중 다른 커미터들에게 큰 커밋을 잠시 보류해 달라는 요청이 있었음
  • JEP 401은 기본 비활성화된
    preview 기능
  • 문법을 사용하려면
    --enable-preview

가 필요함

  • Brian Goetz는 이를 “Valhalla의 첫 번째 부분”이라고 선을 그음

  • JDK 28은 2027년 3월 릴리스 예정이며, mainline 통합은 2026년 7월쯤으로 계획됨

Valhalla가 겨냥한 Java 객체 모델의 비용

  • Valhalla의 표어는 “
    codes like a class, works like an int”임

  • 메서드, 생성자 검증, 의미 있는 필드 이름을 가진 일반 클래스를 쓰면서도 JVM이 primitive처럼 효율적으로 다룰 수 있게 하는 것이 목표임

  • Java에서는 8개 primitive를 제외하면 거의 모든 것이
    참조 타입
    Point p = new Point(1, 2)

에서 p

는 point 자체가 아니라 힙 객체를 가리키는 포인터임

  • 필드를 읽을 때마다 JVM은 포인터를 따라가야 함

  • 객체 수가 늘어나면 비용이 급격히 커짐

  • 각 객체에는 타입, 동기화 상태 등을 위한
    객체 헤더가 있음

  • 객체는 힙에 할당되고 이후 GC 대상이 됨
    Point

백만 개 배열은 실제로 백만 개 포인터와 힙 곳곳의 백만 개 객체로 구성됨

  • Brian Goetz의 “State of Valhalla”는 이런 메모리 배치를
    fluffy라고 부름
  • Valhalla가 원하는 것은 데이터가 나란히 놓이는
    dense한 배치임

하드웨어 격차와 escape analysis의 한계

  • 밀도 높은 메모리 배치가 중요한 이유는 CPU와 메모리 속도 격차 때문임

  • 1995년에는 메모리 접근 비용이 CPU 연산과 비슷했음

  • 현재 CPU는 메인 메모리보다 두 자릿수 배 빠르며, 이 차이를 캐시가 메움

  • CPU는 보통
    64바이트 cache line 단위로 메모리를 읽음

  • 데이터가 조밀하고 순서대로 있으면 한 번에 유용한 값을 많이 가져옴

  • 포인터를 따라 흩어진 객체에 접근하면 cache miss가 발생할 수 있고, hit보다 훨씬 느릴 수 있음

  • JVM의
    escape analysis는 일부 객체 할당을 제거할 수 있음

  • 객체가 로컬 코드 조각 밖으로 “escape”하지 않는다고 판단되면 힙에 할당하지 않고 필드를 변수나 레지스터로 펼칠 수 있음

  • 다만 escape analysis는 예측 가능성이 낮고 취약함

  • 객체가 다른 클래스의 필드에 들어가거나 배열에 저장되거나 복잡한 메서드로 전달되거나 JIT가 분석할 수 없는 경계를 넘으면 최적화가 멈출 수 있음

  • 작은 리팩터링, JDK 업데이트, 코드 구조 변경만으로 객체가 다시 힙에 올라갈 수 있음

  • 성능을 위해 객체를 포기하고
    r

, g

, b

같은 raw byte로 직접 인코딩하면 속도는 얻을 수 있지만 안전성·가독성·검증·메서드를 잃음

2014년 시작과 Q World에서 L World로의 전환

  • Project Valhalla는 공식적으로 2014년에 시작됨

  • James Gosling은 당시 이를 “six PhDs tied into a single knot”라고 표현함

  • Java 창시자들은 Java 1.0 시절부터 value type을 원했지만, 1995년에는 문제가 너무 어려워 포기했음

  • 초기 목표는 프로그래밍 모델과 현대 하드웨어 성능 특성의
    정렬을 회복하는 것이었음

  • 사용자가 직접 primitive처럼 flat하고 dense한 타입을 선언하되, 일반 클래스처럼 보이고 동작하게 만드는 방향임

  • 초기 prototype은
    Q World 방향이었음

  • 새 value type을 객체와 근본적으로 다른 존재로 보고, 별도 type descriptor, bytecode, top type을 두는 방식임

  • JVM 타입 시스템 전체가 두 가지 변형을 가져야 해 복잡성이 커졌음

  • 2019년쯤 등장한
    L World가 전환점이 됨

  • value type이 일반 reference와 같은 “L carrier”를 공유함

  • 팀은 이 통합이 어렵다고 예상했지만, 큰 타협 없이 동작했고 이전 prototype의 여러 문제를 해결함

  • L World에서 중요한 분리가 생김

  • JVM 모델과 언어 모델이 100% 겹칠 필요는 없음

  • JVM에는 L World 모델을 두고, 프로그래머에게는 더 편한 언어 모델을 제공할 수 있음

  • 이후 작업은 value class와 전문화 제네릭의
    두 단계로 나뉨

이름과 모델의 변화

  • Valhalla 용어는 여러 번 바뀌었고, 단순한 명칭 변경이 아니라 모델 변경을 반영함

  • 초기 용어는
    value types였음

  • 당시에는 이 타입들이 정확히 어떤 존재인지 아직 명확하지 않았음

  • 2019~2020년쯤
    inline classes 모델이 자리 잡음

  • 기존 클래스는 identity classes로, 새 클래스는 identity가 없는 inline classes로 구분됨

  • inline class는 기본적으로 final이고, 필드는 final이며, 동기화할 수 없는 제약이 잡힘

  • 2021년 “State of Valhalla”는
    primitive classes와 두 projection 모델을 다룸

  • 하나의 타입이 flat하고 null이 불가능한 value variant와 null을 허용하는 reference variant를 갖는 구상이었음
    Point.val

/ Point.ref

, 이후 Point!

/ Point?

같은 문법도 실험됨

  • 이 모델은 강력했지만
    인지 부담이 컸음

  • 프로그래머가 같은 타입의 두 형태와 변환 시점을 일상적으로 이해해야 했음

  • 결국 사용자 모델을 단순화하기 위해 dualism이 축소됨

  • 현재 JEP 401은
    value classvalue object를 사용함
    value

modifier로 value class를 선언함

  • 인스턴스는 identity가 없는 value object임

  • value class는 여전히 reference type임

  • non-nullability는 별도 optional JEP인 Null-Restricted Value Class Types로 분리됨

  • 이전 “primitive classes” 모델을 설명하는 오래된 글은 현재 OpenJDK 기준과 다를 수 있음

  • JEP 401에는 preview인 JEP 402: Enhanced Primitive Boxing도 함께 있음

  • primitive와 wrapper 사이 변환을 더 부드럽게 만드는 방향임

  • 완성된 형태로 JEP 401과 함께 모두 들어온다고 가정하면 안 됨

JDK 28의 value class 모델

  • value class는
    value

modifier로 선언함

value class USDCurrency implements Comparable<USDCurrency> {
private int cents; // implicitly final
public USDCurrency(int dollars, int cents) {
this.cents = dollars * 100 + cents;
}
public USDCurrency plus(USDCurrency that) {
return new USDCurrency(0, this.cents + that.cents);
}
// dollars(), cents(), compareTo(), toString()...
}
  • value record도 가능함
  • 주요 규칙은 다음과 같음
  • 모든 instance field는
    암묵적으로 final
  • method는
    synchronized

일 수 없음

  • 클래스는 기본적으로 final임

  • value class와 abstract value class로 구성된 계층은 가능함

  • identity가 있는 클래스를 상속할 수 없음

  • interface 구현은 가능함

  • 핵심 특성은
    identity가 없음

  • 일반 객체는 같은 내용을 가져도
    new Point(1, 2)

로 두 번 만들면 서로 다른 객체임

  • value object는
    int

값 4에 “서로 다른 두 개의 4”가 없는 것처럼 identity가 없음

==

, synchronized

, null의 변화

  • value object에서
    ==

는 identity 비교가 아니라 substitutability 검사가 됨

  • 같은 클래스이고 같은 필드 값을 가지는지 재귀적으로 비교함
  • primitive field는 bit 단위로 비교하고, object field는 다시
    ==

로 비교함
new USDCurrency(3,95) == new USDCurrency(3,95)

는 true가 됨

  • 다만
    ==

는 내부 상태를 보기 때문에, “같은 데이터를 표현하는가”에는 보통 equals

가 더 적합함

  • value object에는 동기화할 identity가 없음
  • 동기화를 시도하면
    IdentityException이 발생함
  • identity를 강제 확인해야 할 때는
    Objects.requireIdentity

Objects.hasIdentity

를 사용할 수 있음

  • JDK 28의 value class는 여전히
    null 가능
    USDCurrency d = null;

은 합법임

  • null을 금지하는 타입은 별도 future JEP이며 JDK 28에는 없음

  • non-nullability는 단순 문법 문제가 아니라 더 큰 value class의 평탄화를 여는
    성능 레버가 됨

스칼라화와 힙 평탄화

  • JEP 401은 JVM이 value object를 최적화할 수 있는 자유를 줌
    스칼라화(scalarization) 는 value object reference를 필드 집합으로 분해하는 JIT 기법임
    Color

포인터를 전달하는 대신 r

, g

, b

byte와 null 여부 flag를 전달할 수 있음

  • 할당과 GC 비용이 사라질 수 있음

  • escape analysis와 비슷하지만 더 예측 가능하고, inline되지 않은 method call 경계를 넘어 적용될 수 있음

  • 스칼라화에는 제한이 있음

  • 변수 타입이 value class의 supertype인
    Object

이거나 erased generic parameter이면 보통 동작하지 않음

  • 이 경우 객체가 힙에 materialize되어야 함

힙 평탄화(heap flattening) 는 value object의 필드 값을 compact bit vector로 인코딩해 필드나 배열 셀에 직접 쓰는 방식임

  • 다른 힙 위치를 가리키는 포인터가 필요 없음

  • 데이터 밀도와 locality가 생김

  • 평탄화된 데이터는 concurrent access에서 tearing을 피하기 위해
    atomic하게 읽고 쓸 수 있어야 함

  • 일반 플랫폼에서 “충분히 작은” 크기는 null flag를 포함해 64비트 수준일 수 있음

  • 작은 value class는 잘 평탄화될 수 있지만, 두 개의
    int

필드나 하나의 double

만 있어도 atomic write 크기에 맞지 않아 일반 힙 객체가 될 수 있음

  • 향후에는 128비트 인코딩과 null-restricted type이 더 큰 value class의 평탄화를 가능하게 할 수 있음

Boxing, wrapper, 배열에서의 효과

  • preview가 켜지면
    Integer

, Long

, Double

같은 primitive wrapper class 자체가 value class가 됨

  • box가 identity를 잃으므로 JVM이 스칼라화하고 평탄화할 수 있음
    Integer[]

int[]

의 효율에 가까워지고, boxing overhead가 크게 줄어드는 방향임

  • JEP 402: Enhanced Primitive Boxing은 primitive와 box 사이 변환을 더 확장함
    List<int>

같은 표현으로 가는 길을 열지만, 아직 별도의 성숙 중인 작업임

  • 배열에서 효과가 가장 잘 드러남
  • 기존
    Color[]

는 백만 개 포인터와 힙에 흩어진 백만 개 객체가 될 수 있음

  • value class
    Color[]

는 연속된 색상 값을 직접 저장하는 contiguous block이 될 수 있음

  • CPU는 cache line 단위로 여러 값을 순차적으로 읽을 수 있음

Point[]

예시로 보는 전후 차이

  • Valhalla 이전의 일반 class 예시는 다음과 같음
final class Point {
final int x;
final int y;
Point(int x, int y) { this.x = x; this.y = y; }
}
Point[] points = new Point[1_000_000];
  • 이 배열은 백만 개의
    포인터를 담음
  • 각 포인터는 힙 어딘가의 별도
    Point

객체를 가리킴

  • 각 객체에는 두
    int

외에도 객체 헤더가 있음

  • 순회할 때 포인터를 읽고, 해당 주소로 점프하고, 필드를 읽어야 함

  • Valhalla 이후 value class 예시는 다음과 같음

value class Point {
final int x;
final int y;
Point(int x, int y) { this.x = x; this.y = y; }
}
Point[] points = new Point[1_000_000];
  • 코드 차이는
    value

한 단어지만, 메모리 배치는 달라짐

  • JVM은 각 point의 값을 배열 안에 조밀하게 저장할 수 있음
  • element마다 헤더가 없고 포인터도 없음
    x

, y

int

기준 8바이트와 가능한 null flag로 연속 배치될 수 있음

  • 유지보수성도 유지됨
    Point

는 여전히 이름, 생성자, 검증, method를 가진 class임
int[] xs

, int[] ys

로 쪼개고 index를 맞추는 방식을 피할 수 있음

전문화 제네릭이 아직 남은 이유

  • Java generics는
    type erasure로 구현됨
    List<String>

List<Integer>

는 runtime에서 같은 List

  • type parameter
    T

Object

로 erasure됨

  • erasure는 Java의 기존 코드베이스를 깨지 않고 generics를 도입하기 위한 의도적 선택이었음

  • non-generic class를 generic으로 바꿔도 기존 source file과 compiled class를 깨지 않게 함

  • Valhalla와 erasure는 성능상 충돌함
    List<Point>

에 value object를 넣으면 T

Object

로 erasure되므로 객체가 힙에 materialize되어야 함
Point[]

에서 얻은 평탄화 이점이 ArrayList<Point>

에서는 사라질 수 있음

  • 복구 계획은 두 단계임
    Universal Generics: 언어 수준에서 type variable이 value type까지 다룰 수 있게 함
  • 여전히 erasure를 사용함
    T

필드가 기본적으로 null에서 시작하는 문제 때문에 “null pollution” compiler warning이 생길 수 있음

  • 경고를 해결하면 API가 specialization-ready에 가까워짐

Specialized Generics: JVM 수준에서 concrete type argument별 전문화된 class layout을 생성함

  • 프로젝트 용어로 species와 type restriction이 관련됨
  • 이 단계에서야
    ArrayList<Point>

가 실제 flat memory를 쓸 수 있음

  • JDK 28에는
    full specialized generics가 없음
  • 컬렉션, stream, API가 value type 위에서 flat하고 allocation-free가 되는 것은 future release의 작업임

JDK 28에 있는 것과 없는 것

  • JDK 28에 들어오는 것은 다음과 같음
    value class

value record

선언

  • JDK의 기존 value-based class 중 primitive wrapper 같은 클래스의 value class migration

  • 조건을 만족하는 클래스의 스칼라화와 평탄화

  • 더 저렴한 boxing

  • JDK 28에 없는 것은 다음과 같음

  • null-restricted types

  • full specialized generics

  • 128비트 인코딩

  • 완전히 성숙한 JEP 402

  • preview feature이므로 syntax와 동작은 release마다 feedback에 따라 바뀔 수 있음

  • JDK 28은 LTS가 아님

  • 다음 LTS는 2027년 9월의 JDK 29일 가능성이 큼

  • 많은 회사는 안정화된 Valhalla를 LTS에서 만날 수 있지만, JDK 28 preview가 실제 코드 feedback loop를 시작함

생태계와 코드에 생길 변화

  • 고성능 Java 영역에서 Valhalla는 abstraction을 포기하지 않고 dense data를 다루는 경로가 됨

  • 데이터 처리, vector computation, ML, game development, finance, codec 같은 영역이 해당됨

  • framework와 library는 value-based class migration을 시작할 수 있음

  • identity에 의존하던 코드는 동작 차이를 겪을 수 있음
    ==

가 value object에서 address 비교가 아니라 substitutability 비교가 됨
synchronized

는 value object에서 IdentityException

으로 이어짐

Integer

가 value class가 되어도 대부분의 경우 binary는 계속 link됨

  • 새 compilation error는 이런 타입에 동기화하려는 경우임
    Integer

identity에 의존한 ==

synchronized(someInteger)

는 영향을 받을 수 있음

  • early-access build는 jdk.java.net/valhalla에서 사용할 수 있음

자주 나오는 질문 정리

  • value class는 record와 다름
    record

는 content가 component라는 선택임
value

는 identity를 포기하는 선택임

  • 일반 class, record, value class, value record 조합이 모두 가능함

  • value object는
    ==

로 비교할 수 있음

  • 의미는 address 비교가 아니라 substitutability임
  • 표현하는 데이터의 동등성에는 보통
    equals

가 더 적합함

  • JDK 28 value class는 null이 가능함

  • non-nullable type은 future JEP임

  • 더 큰 value class의 평탄화에도 중요함

  • 빠른 flat
    ArrayList<Point>

는 아직 아님

  • type erasure 때문에 generic collection 안의 객체는 힙에 materialize됨
  • JDK 28에서 평탄화가 직접 작동하는 대표 사례는 value type의 field와 array인
    Point[]

  • escape analysis가 전부를 대신하지는 못함

  • 객체가 field, array, 분석 경계 밖으로 나가면 최적화가 깨질 수 있음

  • value object의 스칼라화는 더 예측 가능하고 method-call boundary를 더 멀리 넘을 수 있음

  • 전체 Valhalla는 여러 release에 걸쳐 확장됨

  • JDK 28은 value class의 첫 preview임

  • specialized generics, null-restricted types, 128비트 인코딩은 future release에 걸친 작업임

AI 자동 생성 콘텐츠

본 콘텐츠는 RSS: GeekNews (한국어)의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0