Project Valhalla : value class

allocProc
8 min readMar 3, 2024

--

Photo by julio angel berroa on Unsplash

이 글은 State of Valhalla Part 1: The Road to Valhalla 를 나름대로 요약하고 정리한 글입니다. OpenJDK 에서 자바를 개선하고자 꽤 오래 전부터 대대적으로 진행하는 프로젝트가 여러가지가 있습니다. (Loom, Amber, Valhalla…) 이 중 Valhalla 는 비교적 최근에야 그 존재를 알았고 어떤 내용의 프로젝트인지 아직 파악하는 중인데 흥미로운 내용이 많습니다. 이 글에서는 Project Valhalla 의 핵심 목표 중 하나인 value class 에 대해 간단히 소개하고자 합니다.

Project Valhalla 는 ‘JVM 에 flat 한 데이터 타입을 만들자!’ 라는 목표로 2014년에 시작한 프로젝트입니다. 클래스가 flat 하다는게 무슨말인가 싶죠? 큼지막하게 나누면 아래같은 3가지 정도의 개념을 새롭게 도입하고자 하는건데요.

  • value class
  • primitive class
  • specialized generics

각각이 무엇을 의미하는지는 차차 설명하겠습니다. 이렇게 보면 Valhalla 는 JVM 에 그저 새로운 기능 추가정도인가 싶지만 목표는 그저 기능 추가가 아닙니다. 근본적으로는 성능 개선이죠. JVM 의 무엇이 문제여서, 뭘 개선하려고 이런 새로운 개념을 도입하는지 살펴봅시다.

JVM 타입 시스템은 크게 3가지로 이루어져 있습니다.

  • primitive (int, long…)
  • object
  • array

내가 구성하고자 하는 데이터를 primitive type 에 담아낼 수 없을 때 object 를 사용하죠. 하지만 object 는 heap 에 할당되고 별도의 obejct header 를 필요로 하며 간접참조를 통해 접근해야합니다.

예를 들어 변수 X, Y 를 가진 Point 라는 객체가 있다고합시다. Point 객체를 원소로 가지는 배열(Point[])의 메모리 레이아웃을 간략하게 그려보면 아래와 같습니다.

이처럼 데이터간의 수많은 간접참조를 기반으로 하는 JVM 의 메모리 레이아웃은 현대 컴퓨터 하드웨어에 맞지않습니다. 90년대 초반 JVM 이 처음 설계되던 시절에는 메모리에 접근해서 데이터를 가져오는데 드는 비용이 CPU 산술연산 한번 수행하는 것과 별 차이가 없었다지만 지금은 아닙니다. 메모리에서 데이터를 한번 가져오게 되면 그 시간동안 CPU 는 수천, 수만번 연산할 수 있는 시간을 낭비하게 됩니다. 어떻게 보면 메모리에 접근한다는 동작의 상대적인 부담이 굉장히 커진거라고 볼 수 있겠네요.
Project Valhalla 는 이와같은 현재 JVM 레이아웃을 바꾸고자 합니다. 간접참조를 없애서 평평하고(flat) 밀집한(dense) 레이아웃으로 바꾸려는겁니다. 물론 기존 추상화나 타입 안정성을 해치지 않으면서요.

Project Valhalla 에서 원하는 레이아웃을 적용해서 앞선 그림을 다시 그려보면 아래와 같습니다.

말그대로 평평하고(간접참조가 없고) 밀집하죠(헤더가 없음).

이상적인 객체지향 세계에서 모든것은 객체여야합니다. 하지만 자바는 그렇지 않습니다. primitive 타입이 존재합니다. 이는 초장기 자바가 객체 지향 언어의 이상과 실제 구현 사이에서 적절하게 타협을 한 겁니다. primitive 타입은 성능과 메모리 사용의 효율성을 높이기 위해 필요했습니다. 게다가 객체 타입보다 메모리를 적게 사용하고, 처리 속도도 빠릅니다.

하지만 이로 인해 객체 지향 프로그래밍의 일관성이 깨지는 측면이 있죠. 별거 아닐 것 같았던 이 간극은 점점 더 큰 문제를 가져옵니다.

2004 년에 제네릭이 추가됩니다. primitive 타입은 제네릭의 타입 파라미터로 쓸 수 없습니다. 이를 적절히 해결하고자 wrapper 타입과 autoboxing 을 고안하지만 근본적인 문제를 해결해주지 못했습니다. 표면적으로만 해결한 듯 보였죠.

2014 년에 람다가 추가되면서 상황은 더 악화됩니다. 일단 람다는 제네릭을 기반으로 구현되었기 때문에, 제네릭에서 발생하는 문제들이 람다에도 고스란히 영향을 미쳤습니다. 때문에 java.util.function 패키지에는 primitive 타입을 위해 특수화된 버전들(IntPredicate, IntToLongFunction 등)을 제공해야했습니다. 그리고 각 primitive 타입마다 특수 처리가 필요한 다양한 함수형 인터페이스가 필요하게 됩니다.

제네릭의 주요 목표 중 하나는 다양한 타입 표현 방식에 대한 추상화를 제공하는 겁니다. 하지만, primitive 타입과 참조 타입의 분리는 이러한 추상화를 실현하는 데 있어 점점 더 큰 장벽이 되었습니다.

라이브러리를 설계하는 개발자의 입장에서 이는 더 심각한 문제가 됩니다. 자바의 컬렉션과 스트림 라이브러리를 한번 보죠.

  • 컬렉션 라이브러리는 primitive 타입에 대한 특수화를 피하고, 대신 모든 타입을 객체로 처리하는 방식을 선택합니다.
  • 반면에 스트림 라이브러리는 int, long, double과 같은 특정 primitive 타입을 위해 특수화된 스트림 타입(IntStream, LongStream, DoubleStream)을 도입합니다.
  • 성능을 최적화하기 위한 시도이지만, 이러한 특수화가 라이브러리 디자인에 복잡성을 추가하고, 더 많은 특수화(IntToLongFunction, PrimitiveIterator.OfInt 등)를 요구하는 상황을 만들었습니다.
  • 결과적으로, primitive 타입과 객체 타입 사이의 간극은 일반적인 자바 사용자와 라이브러리 설계자 모두에게 성능과 추상화 사이에서 선택을 해야만 하는 인지적인 부하를 안겨주게 된 겁니다.

이 모든 문제의 원인

위에서 봤던 Point 배열의 메모리 레이아웃이 저런 그림이 될 수 밖에 없었던 건 자바의 모든 객체가 가져야만 하는 동일성(identity) 때문입니다. 동일성을 가진다는건 곧 객체가 변할 수 있다는 뜻인데요. 객체의 필드를 수정하려면 내가 어떤 객체에 접근할 지 명확히 식별할 수 있어야 하기 때문입니다. (물론 동일성은 이 외에도 다양하게 활용됩니다. (object equality (==), synchronization, System::identityHashCode, weak references 등))

이 동일성 때문에 모든 객체는 참조를 통해 접근할 수 밖에 없습니다. Point[] 그림 처럼요.

Project Valhalla 의 해결책

발할라의 핵심 기능 중 하나는 클래스가 동일성을 포기할 수 있게 하는 겁니다. 그리고 이 처럼 동일성을 포기한 객체를 value object(class) 라 부릅니다. 물론 동일성을 포기하면 유연성을 좀 잃게됩니다. (수정이 불가하며(불변), 레이아웃 다형성(layout-polymorphic)을 가질 수 없죠.)

대신 성능의 향상을 가져옵니다.

더이상 객체마다 오브젝트 헤더를 유지할 필요가 없고 참조를 통해 접근할 필요도 없습니다. 그럼에도 value class 는 메소드, 생성자, 필드, 캡슐화, 인터페이스, 제네릭, 애너테이션 등 일반적인 클래스에 사용 가능한 대부분의 메커니즘을 사용할 수 있습니다.

더 중요한건 value class 를 활용하면 문제 많던 기존의 래핑 클래스를 대체할 수 있습니다. primitive 타입의 필드 하나만 가지는 value class 를 생각봅시다. primitive 타입을 표현하고 객체와의 호환성을 유지하면서도 기존 래핑 클래스가 가지고있던 여러가지 오버헤드를 해결할 수 있습니다. 이른바 primitive class 라고 부를 수 있겠네요!

primitive class 는 클래스의 표현력(메소드, 필드, 캡슐화 등)과 기본 타입의 런타임 동작(빠른 접근 속도, 공간 효율성)을 결합한겁니다.

사실 Project Vahalla 의 슬로건만 봐도 지금까지 말했던 value class 가 바로 떠오르긴 합니다.

Codes like a class, works like an int.

value class 를 명확하게 요약한 한 문장이라고 볼 수 있겠습니다.

객체 지향 프로그래밍의 이점을 누리면서도, primitive 타입의 효율적인 메모리 사용과 성능을 활용할 수 있는 것’ 이게 value class 의 핵심이 되겠습니다.

JDK Project Valhalla 가 제시하는 value class 에 대해 아주 간략하게 알아보았습니다. value class 에 대한 더 자세한 내용과 Project Valhalla 에 포함된 다른 기능들은 별도의 글로 소개하겠습니다.

--

--