Faz um tempo que eu fui fuçar no Project Valhalla e acabei ficando preso.
A proposta parece simples: e se nem todo dado pequeno precisasse virar um objeto completo no heap, com identidade, header e referência?
O projeto roda no OpenJDK desde 2014. Hoje ainda é experimental, mas já dá pra testar em builds early-access e acompanhar o que deve chegar nas próximas versões do Java. O ecossistema fala bastante do Java 26 em diante, e o Valhalla é um dos motivos.
O problema que demorei pra enxergar
Java divide o mundo em dois: primitivos (int, long, boolean...) e objetos (Integer, String, suas classes de domínio...).
Primitivo guarda valor direto na memória. Objeto vive no heap, é acessado por referência e passa pelo GC.
Isso funcionou por décadas. Só que no dia a dia a gente usa muita coisa pequena que na prática é só valor: coordenada, ID, intervalo de data, par de campos. Muitas vezes nem precisa de identidade. Só importa o conteúdo.
Identidade vs valor
Identidade é quando o objeto é único porque existe em um endereço próprio na memória:
String a = new String("b3o");
String b = new String("b3o");
System.out.println(a.equals(b)); // true
System.out.println(a == b); // false
equals compara conteúdo. == compara identidade.
Pra muita regra de negócio, um ponto (3, 4) é só (3, 4). Não importa "qual instância" é.
O custo invisível do modelo atual
Olha uma classe simples:
class Coordenada {
int x;
int y;
}
Na cabeça são 8 bytes. Na prática entram object header, alinhamento, padding e referência. Esse objeto pode facilmente ocupar 16 ou 24 bytes.
Multiplica isso:
Coordenada[] coordenadas = new Coordenada[1_000_000];
Um milhão de objetos. Um milhão de alocações. Um milhão de headers. Cache locality vai embora, memória aumenta e o GC trabalha mais.
Hoje o layout é mais ou menos assim:
[ref] -> { x, y }
[ref] -> { x, y }
[ref] -> { x, y }
O Valhalla quer aproximar disso:
[x][y][x][y][x][y]
Dados contíguos, menos pointer chasing, menos cache miss. Parecido com struct em C ou value type em C#, mas sem abandonar GC e tipagem do Java.
Boxing e generics
Outra dor clássica: generics não aceitam primitivo.
Não existe List<int>. Só List<Integer>.
List<Integer> numeros = List.of(1, 2, 3, 4, 5);
Compila e roda. Só que cada Integer é um objeto com header e identidade. Em coleção grande, o custo some no code review e aparece no profiler.
O que o Valhalla quer resolver
Em linhas gerais:
- tipos sem identidade, comparados por valor
- armazenamento inline na memória (flattening)
- menos alocação desnecessária
- generics especializados sem boxing
- menos pressão no GC
A pergunta que resume tudo: eu preciso saber qual instância é, ou só o valor importa?
Primitive classes na prática
Na sintaxe experimental (em builds recentes também aparece como value class), você declara tipos que se comportam como valor:
primitive class Point {
int x;
int y;
}
Sem identidade. Imutabilidade é favorecida. A JVM pode guardar os campos inline, sem criar um objeto separado no heap para cada instância.
Composição e campos nullable
O interessante é que isso não fica limitado a tipos "planos". Dá pra compor:
primitive class Point {
int x;
int y;
}
primitive class Pessoa {
Point location;
float height;
int[] childrenIds;
String name;
}
Aqui entram detalhes importantes:
-
locationé outro value type. Em cenários favoráveis, a JVM pode achatar (flatten) os campos dePointdentro dePessoa, em vez de ter referência para outro objeto. -
heighté primitivo. Também pode ficar inline. -
childrenIdsé array. Continua sendo referência, mas o array em si pode ser mais compacto dependendo do layout. -
nameéString, tipo de referência nullable. Campos nullable complicam flattening, porquenullquebra layout inline previsível.
Ou seja: nem tudo vira bloco contínuo mágico. Tipos de referência e null ainda têm custo. Mas o modelo já permite expressar melhor o que é "só dado" e o que precisa de identidade.
Arrays achatados
Hoje, Point[] seria um array de referências. Cada posição aponta para outro lugar na memória.
Com Valhalla, um array de value type pode se comportar como bloco de valores:
[x][y][x][y][x][y]
Isso muda o perfil de memória em matrizes, simulações, processamento geométrico e qualquer fluxo com milhões de registros pequenos.
L-World, Q-types e L-types (visão geral)
Sem entrar no nível de especificação da JVM, o modelo interno separa duas "projeções":
| Conceito | Ideia |
|---|---|
| Q-type | Value type não-null, flattenable, sem identidade |
| L-type | Referência tradicional, nullable, compatível com o Java de hoje |
Resumindo: Q é valor puro inline. L é o wrapper/referência que o código legado já conhece.
O L-World é o esforço de unificar primitivos, objetos e value types sem quebrar bytecode e APIs antigas. A ideia não é substituir o Java atual. É fazer os dois mundos conviverem.
Null-restricted types
Outra peça do quebra-cabeça: deixar explícito se um tipo aceita null.
Isso ajuda a JVM a otimizar flattening e melhora segurança. null em campo inline é problema de layout e fonte de NullPointerException escondido.
No exemplo da Pessoa, String name aceita null. Já Point location em contextos null-restricted poderia ser tratado de forma mais agressiva pela JVM.
Specialized Generics
Hoje, List<T> com tipo primitivo ou value type costuma passar por boxing.
Com generics especializados, a ideia é gerar layouts específicos em tempo de compilação. Algo como List<Point> poderia armazenar valores inline, sem empilhar Integer-like wrappers.
Esse é um dos pontos com maior impacto prático, mas também um dos que mais demoram pra amadurecer no ecossistema inteiro (JDK, bibliotecas, frameworks).
Object header e Escape Analysis
Objetos Java carregam header com metadados de runtime (marcas de GC, identidade, etc.). Value types querem reduzir ou eliminar esse custo quando identidade não importa.
A JVM já faz Escape Analysis: o JIT tenta perceber que um objeto não "escapou" do método e pode eliminar a alocação, usando registradores. O problema é que isso não é garantido. Depende do código, do compilador e do caminho de execução.
O Valhalla quer tornar parte dessa otimização estrutural. Não depender só de "talvez o JIT resolva".
Impacto no GC
Menos objetos na heap significa:
- menos tracing
- menos scanning
- menos compactação
- menos pausas
GC moderno sofre com bilhões de micro-objetos. Valhalla ataca exatamente o padrão que a gente cria sem perceber em código "simples".
Onde estamos hoje
Ainda é experimental. Build separada, APIs em evolução, sintaxe em preview.
Nas builds mais recentes, a direção do OpenJDK aponta para value classes (JEP 401) como feature preview nas próximas versões da plataforma. A sintaxe primitive class que aparece em materiais e early-access pode variar conforme a build. Vale sempre olhar a documentação da build que você baixou.
Não é algo pra colocar em produção amanhã. Mas o problema que resolve é real e antigo.
Java ganhou o mundo pela produtividade. O Valhalla pergunta se dá pra manter isso sem pagar caro em todo valor pequeno.
Build pra testar:
https://jdk.java.net/valhalla/
Se você mexe com Java e já olhou pro profiler pensando "por que isso alocou tanto?", vale acompanhar. Não precisa virar especialista em JVM. Só entender o rumo já muda como você enxerga modelagem de dados no código.
Top comments (0)