Compact object headers

Java 25 en JEP 519: zo besparen Compact Object Headers geheugen

Eoin Roberts

8 juli 2026

6 min leestijd
In dit artikel leggen we uit wat object headers zijn, hoe compact object headers het geheugenverbruik verbeteren, en wat de verbeteringen hiervan zijn. Daarnaast laten we met enkele codevoorbeelden de verbeteringen zien.

Waarom Compact Object Headers relevant zijn in Java 25

In Java 25 zijn er verschillende verbeteringen toegepast aan het geheugenbeheer van de JVM (Java Virtual Machine). Eén daarvan is JEP-519 Compact Object Headers, dat voor het eerst werd geïntroduceerd als experimentele functionaliteit in Java 24. In Java 25 is JEP 519 Compact Object Headers gepromoveerd van experimentele functionaliteit naar een product feature. De feature is daarmee geschikt om expliciet in te schakelen met -XX:+UseCompactObjectHeaders, zonder eerst experimental VM options te unlocken.


Wat zijn Object Headers in de JVM?

Wanneer een object in het geheugen wordt opgeslagen, dan worden niet alleen de velden van dat object in het geheugen opgeslagen, maar ook de metadata van dat object. In de JVM wordt deze metadata ook de object header genoemd. Het belangrijkste doel van deze metadata is het beheren van een object tijdens runtime. Zonder een object header zou de JVM niet weten van welk type het object is, hoe erop gesynchroniseerd moet worden of hoe het object tijdens garbage collection beheerd moet worden. De object header is opgedeeld in twee delen: de mark word en de class pointer.

Mark word

De mark word heeft een grootte van 64 bits (8 bytes) en bestaat uit de volgende onderdelen.

  1. De identity hashcode (31 bits).
  2. Leeftijd van het object (4 bits). Dit wordt door de garbage collector gebruikt om te bepalen wanneer het object verplaatst moet worden naar een aparte geheugenruimte voor oudere objecten.
  3. Synchronisatie metadata (2 bits) dat gebruikt wordt om te bepalen of een object gelocked is.
  4. Een deel dat ongebruikt is en oorspronkelijk was gereserveerd voor nieuwe metadata (27 bits).

Class pointer

De class pointer heeft een grootte van 32 bits (4 bytes) en is een referentie naar de klasse waartoe een object behoort. Dankzij deze referentie weet de JVM welk type het object heeft en welke metadata daarbij hoort.


Wat verandert er met Compact Object Headers?

Het idee achter compact object headers is het efficiënter opslaan van object headers, zodat er minder geheugen nodig is. Compact object headers gebruiken maar 64 bits (8 bytes) in plaats van de 96 bits (12 bytes) die nodig zijn voor object headers. Ondanks dat er ook een nieuw onderdeel voor toekomstige uitbreiding toegevoegd is, is het gelukt om de grootte van object headers met een derde te verkleinen. Een compact object header bestaat uit de volgende onderdelen.

  1. De identity hashcode (31 bits, ongewijzigd).
  2. Compressed class pointer (22 bits i.p.v. de oorspronkelijk 32 bits).
  3. Leeftijd van het object (4 bits, ongewijzigd).
  4. Ruimte gereserveerd voor Project Valhalla (4 bits, nieuw).
  5. Synchronisatie metadata (2 bits, ongewijzigd).
  6. Self forwarded tag (1bit, nieuw).

De grootste ruimtebesparing komt uiteraard door het verwijderen van de ongebruikte gereserveerde ruimte. Dit bespaart al gauw 27 bits ruimte. De andere ruimtebesparing wordt gerealiseerd door een wijziging aan hoe de klasse metadata wordt opgeslagen en hoe er naar deze klassen gerefereerd kan worden.

Class pointers

De class pointers waren al geoptimaliseerd doordat ze slechts 32 bits nodig hadden, terwijl ze toch gebruikt konden worden in een 64-bits omgeving. Dit is mogelijk omdat de klasse metadata wordt opgeslagen in een geheugengebied (ook bekend als compressed class space) dat kleiner is dan 4 GB. De waarde die in de 32-bits ruimte wordt opgeslagen, is een offset binnen dit geheugengebied en niet een standaard 64 bits geheugenreferentie.

Om de grootte van de class pointer met 10 bits te verkleinen, is de manier waarop klassen worden opgeslagen in de compressed class space aangepast. In de 32-bits variant is het mogelijk om naar iedere positie in de compressed class space te verwijzen. Door de indeling van de compressed class space te veranderen naar blokken van 1.024 bytes (2 tot de macht 10), is het nu mogelijk om naar deze blokken te verwijzen in plaats van naar iedere afzonderlijke positie. Uiteraard betekent dit dat er tussen de blokken geheugenruimte zal overblijven, maar dit kan ook door andere JVM metadata gebruikt worden. Omdat de blokken 1.024 bytes groot zijn, zijn er 4.194.304 (2 tot de macht 22) beschikbare blokken binnen de compressed class space. Om naar ieder blok te verwijzen, zijn nog maar 22 bits nodig in plaats van 32 bits. Wanneer er een class pointer gebruikt wordt, wordt deze 22-bits verwijzing automatisch omgezet naar de 32-bits variant.


Geheugenverbruik meten met en zonder Compact Object Headers

Om het verschil duidelijk te maken hebben we de code van 2 demo applicaties toegevoegd.

Record-instanties met String-literals

static void main() {
    ClassA[] objectArray = new ClassA[100_000_000];

    for (int i = 0; i < 100_000_000; i++) {
        objectArray[i] = new ClassA("John", "Doe");
    }
}

record ClassA(String firstName, String lastName) {
}

Extra String-objecten

static void main() {
    ClassA[] objectArray = new ClassA[100_000_000];

    for (int i = 0; i < 100_000_000; i++) {
        objectArray[i] = new ClassA(new String("John" + i), new String("Doe" + i));

    }
}

record ClassA(String firstName, String lastName) {
}

In het eerste voorbeeld wordt een record met twee String-velden, firstName en lastName, 100 miljoen keer aangemaakt. Dit resulteert in het aanmaken van 100 miljoen object headers. Het tweede voorbeeld is bijna hetzelfde als het eerste, maar er is een verschil in de manier waarop de strings worden geïnitialiseerd. Het eerste voorbeeld gebruikt string literals, die automatisch in de string pool worden geplaatst. Dit zorgt ervoor dat er uiteindelijk slechts twee strings in het geheugen terechtkomen, ondanks dat de klasse 100 miljoen keer wordt geïnstantieerd.

Het tweede voorbeeld gebruikt de String-klasse en daardoor komen de stringwaarden niet in de string pool terecht, waardoor beide strings 100 miljoen keer worden aangemaakt. Om te zorgen dat de JVM alle string objecten opslaat, wordt de loop index (i) toegevoegd aan beide string waarden. Dit voorkomt dat er features van de JVM de string objecten slimmer opslaat. Het geheugengebruik van het tweede voorbeeld zal daardoor veel groter zijn dan dat van het eerste voorbeeld. Het verschil is bedoeld om de geheugenreductie te laten zien tussen toepassingen waarin het totale geheugengebruik en de verhouding van object header geheugengebruik en object geheugengebruik verschillend is.

Hieronder zien we het verschil in geheugengebruik tussen de verschillende versies en met compact object Headers uit en aan. Dit is getest met Java 25 en om compact object headers aan te zetten wordt er gebruik gemaakt van de command line optie
XX:+UseCompactObjectHeaders.

Compact Object HeadersDemo 1 (in MB)Demo 2 (in MB)
Uit2.83513.852
Aan2.02111.544

In demo 1 is het geheugengebruik met 814MB (28,7%) verminderd en in demo 2 is het geheugengebruik met 2.308MB (16,6%) verminderd. De absolute besparing in demo 2 is bijna 3 keer zo groot, wat komt omdat er door de string objecten 3 keer zo veel objecten en object headers in het geheugen worden opgeslagen. Maar omdat er in demo 2 meer geheugen wordt gebruikt, is de relatieve besparing minder. Dit is slechts een simpel voorbeeld en in het echt zullen er meer verschillende klassen en grotere objecten in het geheugen opgeslagen zijn. De geschatte waarden in het echt variëren van 10% tot 20%, afhankelijk van de gebruikte klassen en objecten.

Naast minder geheugengebruik, hebben de compact object headers ook andere voordelen.
Er passen meer object in de CPU cache en dat kan ervoor zorgen dat de CPU minder vaak het geheugen hoeft te raadplegen. Dit kan ervoor zorgen dat de applicatie sneller zal draaien.
Door het verminderde geheugengebruik hoeft de garbage collector minder data te verplaatsen wanneer dat nodig is. Dit zal ervoor zorgen dat de garbage collector wat minder tijd nodig heeft.

Conclusie

Compact object headers zorgen voor een aanzienlijke verbetering van het geheugengebruik, zonder dat ontwikkelaars hiervoor wijzigingen aan hun code hoeven aan te brengen. Dit was in Java 24 nog experimenteel, maar sinds Java 25 is het een feature die in de JVM ingeschakeld kan worden. In de voorbeelden in dit artikel hebben we gezien dat het geheugengebruik 28,7% en 16,6% afnam, maar in het echt zal dit waarschijnlijk tussen 10% en 20% liggen. Compact object headers verbeteren niet alleen het geheugengebruik, maar zorgen ook voor efficiënter gebruik van de CPU-cache en snellere garbage collection.

Foto van Eoin Roberts

Eoin Roberts