boolesk og boolesk[] Hukommelseslayout i JVM
1. Oversigt
I denne hurtige artikel skal vi se, hvad fodaftrykket er for en boolesk værdi i JVM under forskellige omstændigheder.
Først vil vi inspicere JVM'en for at se objektstørrelserne. Så vil vi forstå rationalet bag disse størrelser.
2. Opsætning
For at inspicere hukommelseslayoutet af objekter i JVM'et, kommer vi til at bruge Java Object Layout (JOL) i vid udstrækning. Derfor skal vi tilføje jol-kernen afhængighed:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
3. Objektstørrelser
Hvis vi beder JOL om at udskrive VM-detaljerne i form af objektstørrelser:
System.out.println(VM.current().details());
Når de komprimerede referencer er aktiveret (standardadfærden), ser vi outputtet:
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
I de første par linjer kan vi se nogle generelle oplysninger om VM'en. Derefter lærer vi om objektstørrelser:
- Java-referencer bruger 4 bytes, boolesk s/byte s er 1 byte, char s/kort s er 2 bytes, int s/float s er 4 bytes og endelig lange s/dobbelt s er 8 bytes
- Disse typer bruger den samme mængde hukommelse, selv når vi bruger dem som array-elementer
Så i nærværelse af komprimerede referencer, hver boolsk værdien tager 1 byte. På samme måde er hver boolean på et boolsk[] bruger 1 byte. Justeringsudfyldninger og objektoverskrifter kan dog øge den plads, der forbruges af boolesk og boolesk[] som vi vil se senere.
3.1. Ingen komprimerede referencer
Selv hvis vi deaktiverer de komprimerede referencer via -XX:-UseCompressedOops , den boolske størrelse ændres overhovedet ikke :
# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
På den anden side tager Java-referencer dobbelt så meget hukommelse.
Så på trods af hvad vi kunne forvente i starten, booleans bruger 1 byte i stedet for kun 1 bit.
3.2. Ordrivning
I de fleste arkitekturer er der ingen måde at få adgang til en enkelt bit atomisk. Selvom vi ville gøre det, ville vi sandsynligvis ende med at skrive til tilstødende stykker, mens vi opdaterede en anden.
Et af designmålene med JVM er at forhindre dette fænomen, kendt som ordrivning . Det vil sige, i JVM'en skal hvert felt og array-element være adskilt; opdateringer til ét felt eller element må ikke interagere med læsninger eller opdateringer af noget andet felt eller element.
For at opsummere er adresserbarhedsproblemer og ordrivning hovedårsagerne til boolesk s er mere end blot én enkelt bit.
4. Almindelige objektpegere (OOP'er)
Nu hvor vi kender boolesk s er 1 byte, lad os overveje denne simple klasse:
class BooleanWrapper {
private boolean value;
}
Hvis vi inspicerer hukommelseslayoutet for denne klasse ved hjælp af JOL:
System.out.println(ClassLayout.parseClass(BooleanWrapper.class).toPrintable());
Så vil JOL udskrive hukommelseslayoutet:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 1 boolean BooleanWrapper.value N/A
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
Den BooleanWrapper layout består af:
- 12 bytes for overskriften, inklusive to mærker ord og én klasse ord. HotSpot JVM bruger mærket ord til at gemme GC-metadata, identitetshashkode og låseinformation. Den bruger også klassen ord til at gemme klassemetadata såsom kontrol af runtime type
- 1 byte for den faktiske boolean værdi
- 3 bytes udfyldning til justeringsformål
Som standard skal objektreferencer justeres med 8 bytes. Derfor tilføjer JVM 3 bytes til 13 bytes header og boolesk for at gøre det til 16 bytes.
Derfor boolesk felter kan bruge mere hukommelse på grund af deres feltjustering.
4.1. Brugerdefineret justering
Hvis vi ændrer justeringsværdien til 32 via -XX:ObjectAlignmentInBytes=32, derefter ændres det samme klasselayout til:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 1 boolean BooleanWrapper.value N/A
13 19 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 19 bytes external = 19 bytes total
Som vist ovenfor tilføjer JVM 19 bytes polstring for at gøre objektstørrelsen til et multiplum af 32.
5. Array OOPs
Lad os se, hvordan JVM'et opstiller en boolesk array i hukommelsen:
boolean[] value = new boolean[3];
System.out.println(ClassLayout.parseInstance(value).toPrintable());
Dette vil udskrive instanslayoutet som følger:
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) # mark word
4 4 (object header) # mark word
8 4 (object header) # klass word
12 4 (object header) # array length
16 3 boolean [Z.<elements> # [Z means boolean array
19 5 (loss due to the next object alignment)
Ud over to mark ord og én klasse ord,array pointers indeholder yderligere 4 bytes til at gemme deres længder.
Da vores array har tre elementer, er størrelsen af array-elementerne 3 bytes. Men disse 3 bytes vil blive polstret med 5 feltjusteringsbytes for at sikre korrekt justering.
Selvom hver boolesk element i et array er kun 1 byte, hele arrayet bruger meget mere hukommelse. Med andre ord, vi bør overveje headeren og polstringen overhead, mens vi beregner matrixstørrelsen.
6. Konklusion
I dette hurtige selvstudie så vi det booleske felter bruger 1 byte. Vi lærte også, at vi skulle overveje header- og polstringsomkostningerne i objektstørrelser.
For en mere detaljeret diskussion anbefales det stærkt at tjekke ups-sektionen af JVM-kildekoden. Aleksey Shipilëv har også en meget mere dybdegående artikel på dette område.
Som sædvanlig er alle eksemplerne tilgængelige på GitHub.