Hlavní navigace

Velikost objektů v Javě

Karel Čížek

O Javě se říká, že je paměťově příliš náročná. Z části je to pravda, ale často jde jen o nešetrné programy, které nerespektují jak Java alokuje paměť. V článku si ukážeme jak spočítat velikost objektů a polí. To obvykle stačí k tomu, abychom psali aplikace, které si místo gigabajtů vystačí se stovkami megabajtů.

Než můžeme začít mluvit o velikosti celých objektů a polí, musíme vědět, kolik paměti zabírají jednotlivé primitivní typy. To ukazuje následující tabulka. Za povšimnutí stojí fakt, že boolean vyžaduje 1 bajt, i když reprezentuje jenom jeden bit informací. To stejné platí i o poli, kde si každý bool vyžádá celý jeden bajt1.

typ velikost
byte, boolean 1 B
short, char 2 B
int, float 4 B
long, double 8 B

Objekty

Každý objekt (aspoň na nejrozšířenějším JVM od Oracle2) začíná hlavičkou o délce dvou procesorových slov, která obsahují hashCode identity objektu3, ukazatel na třídu objektu a nějaké další příznaky. Na 32 bitových platformách (nebo 64 bitových s komprimovanými pointery) to představuje celkem 8 bajtů, na 64 bitových pak 16 bajtů. Za hlavičkou následují všechny atributy objektu (fields, instanční proměnné). Jejich pořadí se řídí pěti pravidly:

  1. Každý objekt je zarovnán na násobek 8 bajtů.
  2. Atributy objektů jsou řazeny podle velikosti: nejdřív long/double, pak int/float, char/shorts, byte/boolean a jako poslední reference na jiné objekty. Atributy jsou vždy zarovnány na násobek vlastní velikosti.
  3. Atributy patřící různým třídám hierarchie dědičnosti se nikdy nemíchají dohromady. Atributy předka se v paměti nacházejí před atributy potomků.
  4. První atribut potomka musí být zarovnán na 4 bajty, takže za posledním atributem předka může být až tříbajtová mezera.
  5. Pokud je první atribut potomka long/double a předek není zarovnán na 8 bajtů, long/double se může přesunout až na konec potomkových atributů, aby menší typy vyplnily čtyřbajtovou mezeru.

Atributy jsou zarovnány na násobek vlastní velikosti proto, že pro procesor je obvykle rychlejší načíst například 4 bajty paměti do čtyřbajtového registru, pokud se nachází na adrese zarovnané právě na 4 bajty. Kdyby JVM zachovávalo pořadí atributů a zároveň je zarovnávalo, objekty by byly plné nevyužitých děr. Tím, že atributy seřadí od největších (long/double) po nejmenší (byte/bool), dosáhne minimální velikosti objektu, ve kterém jsou všechny atributy přirozeně zarovnány.

Ukážeme si několik příkladů, jak vypadá paměť alokovaná hypotetickými objekty (všechny příklady uvažují 32bitové JVM):

class X { byte b; int i; long l; } 
| header        | long          | int   |b|xxxxx|
| 8B            | 8B            | 4B    |1| 3B  |
|---------------|---------------|-------|-|-----|
class Parent { int pi; short ps; }
class Child { int ci; short cs; } extends Parent 
| header        | pi    |ps |xxx| ci    |cs |xxx|
| 8B            | 4B    |2B |2B | 4B    |2B |2B |
|---------------|-------|---|---|-------|---|---|
class Parent { short s; byte b; }
class Child { long l; int i; } extends Parent 
| header        |s  |b|x| int   | long          |
| 8B            |2B |1|1| 4B    | 8B            |
|---------------|---|-|-|-------|---------------|
class Cons { Object head; Object tail; } 
| header        | head  | tail  |
| 8B            | 4B    | 4B    |
|---------------|-------|-------|

Pole

Pole jsou na tom podobně jako objekty, ale jejich hlavička kromě dvou procesorových slov obsahuje ještě jeden čtyřbajtový integer udávající délku pole. Pak následuje samotný obsah pole, zase zarovnán na násobek 8 bajtů.

Pokud pole obsahuje osmibajtové primitivní typy (long nebo double), hodnoty musejí být zarovnány na 8 bajtů, takže za hlavičkou je čtyřbajtová mezera a teprve pak následují data.

new byte[] { 0, 0 } 
| header        |length |b|b|xxx|
| 8B            | 4B    |1|1|2B |
|---------------|-------|-|-|---|
new long[] {0} 
| header        |length |xxxxxxx| long          |
| 8B            | 4B    | 4B    | 8B            |
|---------------|-------|-------|---------------|

Situace se dá shrnout do několika vzorců:

32bitové platformy

Objekty
8B hlavičky + velikost atributů předků zarovnaných na 4 bajty + součet velikostí všech typů zarovnaný nahoru na 8B
Pole typů byte, bool, short, char, int, float
12B hlavičky + length * velikost typu to celé zarovnané nahoru na 8 bajtů
Pole referencí
12B hlavičky + length * 4B to celé zarovnané nahoru na 8 bajtů
Pole typů long nebo double
12B hlavičky + 4B padding + length * 8B

64bitové platformy

Objekty
16B hlavičky + velikost atributů předků zarovnaných na 4 bajty + součet velikostí všech typů zarovnaný nahoru na 8B
Pole typů byte, bool, short, char, int, float
20B hlavičky + length * velikost typu to celé zarovnané nahoru na 8 bajtů
Pole typů long, double nebo referencí
20B hlavičky + 4B padding + length * 8B

Příklady

Nakonec si ukážeme několik příkladů ukazujících, kolik paměti spotřebují některé běžně používané typy.

String

Řetězec je reprezentován jako objekt, který odkazuje na vnitřní pole znaků.

Samotný objekt String má: 8B hlavičky, 4B hashcode, 4B délka stringu, 4B offset, 4B reference

Vnitřní pole má: 8B hlavičky, 4B délku pole + (počet znaků) * 2B

Stringová část zabírá 24 bajtů, vnitřní pole má režii 12 bajtů + 0 až 6 bajtů, které se ztratí zarovnáním pole. Dohromady to dělá 36 – 42 extra bajtů na jeden String nebo 60 – 66 bajtů pro 64 bitové platformy.

(pozn: V nejnovější verzi Javy se změnila implementace řetězců a už neobsahují atributy pro délku a offset. Režie je tedy o 8 bajtů menší).

List

Neměnný spojový seznam (jako například ve Scale) je složený z řetězu Cons buněk ukončených buňkou Nil. Nil má jenom jednu instanci v celém virtuálním stroji a tak se jí nemusíme zabývat. Cons obsahuje dva atributy: vlastní hodnotu head a referenci tail na následující buňku v řadě. Protože je List generický a JVM provádí type erasure, head je vždy reference. Pokud odkazuje na primitivní typ, ten je autoboxingem zabalen do objektu.

Cons tedy zabírá: 8B hlavičky + 2 reference po 4B, tedy 16 bajtů na jednu Cons buňku (nebo 32 na 64 bitových platformách). To je celkem přijatelná daň za to, že můžeme v konstantním čase číst a manipulovat začátek seznamu.

Ale teď si představme, že v této datové struktuře budeme chtít ukládat celá čísla a ty musejí projít autoboxingem.

Objekt Integer má: 8B hlaviček, 4B dat a 4B, které padly na oltář zarovnávání paměti, což představuje dalších 12 extra bajtů režie. Dohromady tedy potřebujeme 28 bajtů (nebo 56 bajtů na 64 bitových platformách), abychom mohli uložit 4 bajty dat do spojového seznamu. Naproti tomu pole primitivních integerů má pouze konstantní režii 12B, která je velice rychle amortizována. V takových případech stojí za to zvážit, jestli nebude vhodnější použít nějakou kompaktnější datovou strukturu jako třeba pole, Vector nebo pro extrémní případy zvolit specializované kolekce jako Trove nebo Colt.

HashMap

Existuje mnoho způsobů, jak implementovat hashmapu, ale my budeme uvažovat způsob, jak je implementována ve standardní knihovně Javy – tedy polem, které ukazuje na spojový seznam (tzv. closed addressing):

class Map<K, V> {
  Entry<K, V>[] buckets;
}
class Entry<K, V> {
  Bucket<K, V> next;
  K key;
  V value;
} 

Potřebujeme pole referencí, které je velké přibližně jako počet elementů v mapě. Každá reference, která obsahuje nějakou hodnotu, vede na Entry, která má tři reference: další Entry v řadě, klíč mapy a hodnotu.

Entry zabere 8B hlavičky + 12B reference + 4B zarovnání. Dohromady to dává 24B (nebo 40B na 64 bitových platformách) + 4B reference na jeden pár klíč/hodnota v poli, a to nepočítáme data, která zaberou samotné objekty klíčů a hodnot a případné paměťové náklady spojené s autoboxingem.


Odkazy:

Pozn:

  1. Pokud chceme kompaktní pole, můžeme použít BitSet nebo BitMap
  2. Jednotlivé virtuální stroje se od sebe mohou lišit tím, jak v paměti reprezentují objekty.
  3. Vrací ho standardní implementace metody hashCode, nebo se k němu můžeme dostat voláním java.lang.Sys.identityHashCo
Našli jste v článku chybu?

21. 1. 2013 11:11

NetBeans nejsou editor, ale IDE. Nevím, jak v případě C/C++, ale v případě Javy drží v paměti informace o projektu a všech třídách a vazbách, aby programátorovy usnadnily práci (to je účel IDE, kdyby ty nápovědy a pomůcky nechtěl, použije jen editor). To, že IDE potřebuje hodně paměti, vychází z toho, jaké služby má poskytovat – a ve spotřebě paměti na desktopu má IDE konkurenci podle mne jen ve zpracování videa a velkých obrázků.

21. 1. 2013 12:24

Možná by bylo někdy nejlepší mít spojový seznam polí vhodných délek.

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Podnikatel.cz: Změny v cestovních náhradách 2017

Změny v cestovních náhradách 2017

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

Vitalia.cz: Jmenuje se Janina a žije bez cukru

Jmenuje se Janina a žije bez cukru

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Root.cz: Certifikáty zadarmo jsou horší než za peníze?

Certifikáty zadarmo jsou horší než za peníze?

Podnikatel.cz: Snížení DPH na 15 % se netýká všech

Snížení DPH na 15 % se netýká všech

Vitalia.cz: Pamlsková vyhláška bude platit jen na základkách

Pamlsková vyhláška bude platit jen na základkách

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Vitalia.cz: Když přijdete o oko, přijdete na rok o řidičák

Když přijdete o oko, přijdete na rok o řidičák

120na80.cz: Na ucho teplý, nebo studený obklad?

Na ucho teplý, nebo studený obklad?

Podnikatel.cz: Udávání kvůli EET začalo

Udávání kvůli EET začalo

Vitalia.cz: Proč vás každý zubař posílá na dentální hygienu

Proč vás každý zubař posílá na dentální hygienu

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Měšec.cz: mBank cenzuruje, zrušila mFórum

mBank cenzuruje, zrušila mFórum

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy