메뉴
HN
Hacker News 40일 전

10년의 결실, JDK 28에 도착하는 프로젝트 발할라

IMP
9/10
핵심 요약

자바 생태계의 가장 중요한 변화 중 하나인 '프로젝트 발할라(Project Valhalla)'가 드디어 JDK 28에 프리뷰 기능으로 통합될 예정입니다. 이는 객체 지향의 편리함을 유지하면서도 기본형(primitive) 수준의 메모리 효율과 성능을 내기 위한 자바의 장기적인 노력의 결실입니다. 개발자들은 기존 코드의 호환성을 깨지 않고도 메모리와 CPU 캐시를 훨씬 더 효율적으로 활용하는 최적화된 코드를 작성할 수 있게 됩니다.

번역된 본문

원문 제목: 10년의 노력이 JDK 28에 도착하다, 프로젝트 발할라 설명 출처: JVM Weekly vol. 180

새로운 JVM Weekly가 돌아왔습니다... 마침내 JDK에 발할라(Valhalla)가 포함되면서, 사실상 자바 세계의 라그나로크(종말, 즉 대변혁)가 찾아온 듯합니다. 하지만 상황은 조금 복잡합니다. Artur Skowronski, 2026년 6월 18일

6월 15일, 오라클 엔지니어 Lois Foltan은 많은 업계 관계자들이 이제는 이루어지지 않을 거라 믿었던 것을 확정 지었습니다. 바로 'JEP 401: 값 클래스와 객체(Value Classes and Objects)'가 메인 OpenJDK 저장소에 통합되며, JDK 28을 목표로 하고 있다는 소식입니다.

이번 변경 사항은 매우 방대하여, 통합 작업이 진행되는 동안 나머지 커미터들에게는 큰 규모의 커밋을 보류해 달라고 요청했습니다. 이 풀 리퀘스트(PR) 하나만 해도 무려 1,816개의 파일에 걸쳐 약 19만 7천 줄의 코드가 추가되었습니다.

하지만 샴페인을 터뜨리기 전에 알아둘 것이 있습니다. 이것은 기본적으로 비활성화된 '프리뷰(preview)' 기능이며, Brian Goetz가 사람들의 기대를 신속히 식혔듯이 이는 '발할라의 첫 번째 부분'일 뿐이라는 점입니다. Goetz는 날카로운 관찰을 덧붙였습니다. '그들은 절대 출시하지 못할 거야'라고 말하던 무리가 이제 곧바로 '하지만 가장 중요한 부분은 출시하지 않았잖아'라고 부드럽게 화제를 전환할 것이라는 점입니다. (사실 수년간 커뮤니티에는 프로젝트가 출시되기도 전에 우리가 먼저 북유럽 신화의 사후세계인 발할라에 가게 될 거라는 농담이 돌아다니곤 했습니다.) 아무튼 비판자를 만들려면 그만한 일을 해낸 증거이기도 합니다.

그래서 지금이 전체적인 이야기를 전달하기에 완벽한 시점입니다. 이번 호는 여러분이 발할라에 관한 작업을 지금까지 전혀 따라가지 않았다는 가정하에 쓰인 거대한 심층 분석 기사입니다. 2014년의 문제 인식부터 시작해, 아이디어의 발전 과정(상당수는 결국 폐기되었습니다), 그리고 JDK 28에서 우리가 정확히 무엇을 손에 쥐게 될 것인지까지 다룹니다. 커피를 한 잔 타세요. 저는 이 칼럼을 오랫동안 갈무리해 두었다가 바로 이 기회를 위해 꺼내든 것입니다.

  1. 서론 - 대체 이게 무슨 일인가?

발할라가 처음부터 품고 있는 슬로건은 이렇습니다. "클래스처럼 코딩하고, int처럼 동작한다."

이 한 문장이 프로젝트의 핵심 목적을 완벽히 요약합니다. 우리는 메서드와 생성자의 유효성 검사, 그리고 합리적인 필드 이름을 갖춘 일반적이고 가독성 좋은 클래스를 작성하길 원하지만, 동시에 JVM이 이를 기본형(primitive)만큼이나 효율적으로 다뤄주길 바라는 것입니다.

왜 이것이 문제가 되는지 이해하려면 자바의 근간으로 돌아가야 합니다. 이 언어에서는 8개의 기본형(int, long, double, boolean 등)을 제외한 모든 것은 '참조형(reference type)'입니다. Point p = new Point(1, 2)라고 작성할 때, 변수 p는 Point가 아닙니다. 변수 p는 포인터, 즉 코트를 맡길 때 받는 번호표 같은 것입니다. 힙(Heap) 어딘가에 객체가 존재하고, 당신은 그 주소가 적힌 종이 쪽지를 들고 있는 셈입니다. 필드를 읽을 때마다 JVM은 이 '코트 보관실'로 가야 하며, 포인터를 통한 우회 참조(pointer indirection)를 수행해야 합니다.

객체 하나만 볼 때는 아무 문제가 아닙니다. 하지만 규모가 커지면 문제가 시작됩니다. 힙에 있는 모든 객체는 각자의 헤더를 갖습니다. (10바이트 남짓의 메타데이터로, JVM이 이 객체의 타입이 무엇인지, 그리고 누군가 이 객체에 동기화를 시도하고 있는지 등을 파악하게 해줍니다.) 덧붙여, 이것은 최근 '프로젝트 릴리푸트(Project Lilliput)'가 객체 헤더 크기를 줄여 해결하려고 했던 바로 그 문제이기도 합니다.

하지만 헤더 크기가 전부는 아닙니다. 모든 객체는 메모리에 할당(allocate)되어야 하고, 결국 가비지 컬렉션(GC)의 대상이 되어야 합니다. 그리고 객체들이 힙 전체에 흩어져 있기 때문에, 백만 개의 Point 객체를 담는 배열은 실제로 거대한 창고 전체에 흩어져 있는 백만 개의 상자를 가리키는 백만 장의 종이 번호표와 다를 바 없습니다.

Brian Goetz는 그의 'State of Valhalla' 문서에서 이러한 메모리 배치 방식을 '푹신한(fluffy)', 즉 부풀어 오르고 비대하다고 불렀습니다. 우리가 꿈꾸는 것은 데이터가 나란히 놓인 조밀한(dense) 메모리 구조입니다.

왜 조밀함이 중요할까요? 하드웨어가 자바보다 더 빠르게 변화했기 때문입니다. 1995년만 해도 메모리 접근 비용은 CPU 연산 비용과 거의 비슷했습니다. 하지만 오늘날 CPU는 주 메모리보다 두 자릿수(약 100배) 더 빠르며, 이 격차는 전적으로 캐시(cache)로 메워집니다. 프로세서는 '캐시 라인(보통 64바이트)'이라고 불리는 덩어리 단위로 메모리를 읽습니다. 만약 데이터가 조밀하고 질서 있게 놓여 있다면, 이런 하나의 덩어리가 읽힐 때 유용한 수많은 값들을 한 번에 가져오게 됩니다. 하지만 포인터를 따라 여기저기 뛰어다녀야 한다면, 메모리에 접근할 때마다 캐시 미스(cache miss)의 위험을 감수해야 하는 것과 같습니다.

원문 보기
원문 보기 (영어)
Project Valhalla, Explained: How a Decade of Work Arrives in JDK 28 - JVM Weekly vol. 180 The new JVM Weekly is here... and Ragnarok seems to come, as we finally have Valhalla in the JDK. However, situation is a bit... nuanced. Artur Skowronski Jun 18, 2026 9 1 Share On June 15, Oracle engineer Lois Foltan confirmed what a good chunk of the industry had stopped believing: JEP 401: Value Classes and Objects will be integrated into the main OpenJDK repository and is targeting JDK 28. Thanks for reading JVM Weekly! Subscribe for free to receive new posts and support my work. Subscribe The change is so large that the remaining committers were asked to hold off on bigger commits during the integration. The pull request alone adds over 197 thousand lines of code across 1,816 files. Before we pop the champagne, though: this is preview , disabled by default, and, as Brian Goetz was quick to cool everyone down, “only the first part of Valhalla.” Goetz added a great observation that the “they’ll never ship it” crowd will now smoothly switch over to “but they didn’t ship the most important part” (and a joke has been going around the community for years that we’ll sooner end up in Valhalla ourselves, the Norse-afterlife one, than the project ships). You have to earn your own haters. So this is a good moment to tell the whole story. This issue is one big deep-dive, written on the assumption that you’ve never followed the work on Valhalla before : from the 2014 problem, through the evolution of ideas (a fair number of which ended up in the trash), all the way to what exactly we’ll be getting our hands on in JDK 28. Brew yourself a coffee. I’ve been sitting on this edition for a long time, saving it for exactly this occasion. 1. Introduction - what this is even about The slogan Valhalla has carried from the start is: “codes like a class, works like an int.” In a single sentence it captures the whole point of the project: we want to write normal, readable classes with methods, constructor validation, and sensible field names, but we want the JVM to be able to treat them as efficiently as primitives. To understand why this is a problem, you have to go back to Java’s foundation. In this language, with the exception of the eight primitives (int, long, double, boolean, and the rest), everything is a reference type . When you write Point p = new Point(1, 2), the variable p isn’t a point. The variable p is a pointer, a coat-check number: somewhere on the heap sits an object, and you’re holding a slip of paper with its address. Every time you want to read a field, the JVM has to “go to the coat check,” performing a hop through the pointer ( pointer indirection ). For a single object, that’s nothing. The problem starts at scale. Every object on the heap has its own header (a dozen-or-so bytes of metadata: among other things, so the JVM knows what type it is and whether anyone is synchronizing on it). Incidentally, this is exactly the problem Project Lilliput has been tackling lately, helping to shrink object header sizes. But header size isn’t everything. Every object has to be allocated , and later garbage collected . And since objects are scattered across the heap, an array of a million Points is in practice a million slips of paper pointing at a million boxes strewn across the whole warehouse. Brian Goetz, in his “State of Valhalla” documents , calls such a memory layout “fluffy” : puffed up, bloated. What we dream of is a dense layout, one where the data lies side by side. Why does density matter? Because the hardware changed faster than Java did . In 1995, a memory access cost roughly the same as a CPU operation. Today the CPU is two orders of magnitude faster than main memory, and the whole gap is bridged by the cache. The processor reads memory in chunks called cache lines (usually 64 bytes). If the data lies densely and in order, one such chunk brings in a ton of useful values at once. If we’re hopping across pointers, every access risks a cache miss , and that can be a hundred times slower than a hit. This is locality of reference , and it’s the real stake in this whole game. “But the JVM has escape analysis,” someone sharp will say. True: the virtual machine can recognize that some object never “escapes” beyond a local fragment of code, and then it doesn’t allocate it at all. From the programmer’s point of view it looks as if the object exists, but in reality its fields get spread out into ordinary variables or CPU registers. In the best case, the cost of allocation and the later cleanup by the garbage collector drops to practically zero. The trouble is that this optimization is unpredictable and fragile. It works only when the JIT compiler can trace the object’s entire flow with high confidence. But all it takes is for the object to land in a field of another class, get stored in an array, get passed into a more complex method, or appear beyond the boundary of code the JIT can analyze, and the whole trick stops working. The source code stays identical, but the performance behavior can change dramatically. This is precisely why experienced JVM programmers treat escape analysis as a nice bonus, not a project’s foundation. If an application’s performance depends on whether a particular JIT version manages to apply this optimization, it’s very easy to fall into the trap of hard-to-predict regressions. A minor refactor, a JDK update, or a change in code structure can send objects back onto the heap, and the costs of allocation and garbage-collector work return in full force. That leaves the brute-force option: give up on objects and encode the data by hand. Instead of a Color class, hold three bytes r, g, b. This isn’t just an academic example. The approach has been used for years in game engines, graphics libraries, image-processing systems, databases, analytics engines, and HPC code, where every byte of memory and every allocation matters. The trouble is that the speed comes at the cost of safety and readability. We lose names, private state, validation, and methods. JEP 401 gives a simple example: a developer working on “raw” color bytes might mistakenly interpret them as BGR instead of RGB, swap red with blue, and quietly corrupt the entire image. A class wouldn’t have allowed it. A bare int? Sure it would. And it’s exactly this dichotomy, either convenient classes, or fast primitives , that Valhalla is trying to erase. 2. The beginnings - 2014, “six PhDs,” and five prototypes Officially, Project Valhalla started in 2014 . James Gosling described it at the time as “six PhDs tied into a single knot,” and that was no exaggeration. Interestingly, the idea is older than the project itself: Java’s creators wanted value types as early as the first version of the language, but in 1995 they gave up, because the problem was too hard. The goal was set ambitiously: to restore alignment between the programming model and the performance characteristics of modern hardware . In other words, to let programmers declare their own types that are flat and dense in memory like primitives, but look and behave like normal classes. Easier said than done. Over the following years the team built five different prototypes , each probing a different aspect of the problem. And this is where the most interesting part of the story begins, because to appreciate Valhalla’s current shape, you have to see how many ideas died along the way. The early prototypes went in a direction we now call “Q World.” It assumed that the new value types were a fundamentally different beast from objects, with separate type descriptors, separate bytecodes, and separate top types, exactly like primitives. Sounds logical: if they’re supposed to work like int, let them be represented like int. The trouble is that such a separation flooded the entire JVM type system with extra complexity: everything had to be done in two variants. The breakthrough came with a prototype christened “L World” (roughly around 2019). The n