booleano e booleano Layout di memoria nella JVM

1. Panoramica

In questo rapido articolo, vedremo qual è l'impronta di un valore booleano nella JVM in diverse circostanze.

Innanzitutto, ispezioneremo la JVM per vedere le dimensioni degli oggetti. Quindi, capiremo la logica alla base di queste dimensioni.

2. Configurazione

Per ispezionare il layout di memoria degli oggetti nella JVM, utilizzeremo ampiamente JOL (Java Object Layout). Pertanto, dobbiamo aggiungere la dipendenza jol-core :

 org.openjdk.jol jol-core 0.10 

3. Dimensioni degli oggetti

Se chiediamo a JOL di stampare i dettagli della VM in termini di dimensioni degli oggetti:

System.out.println(VM.current().details());

Quando i riferimenti compressi sono abilitati (il comportamento predefinito), vedremo l'output:

# 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]

Nelle prime righe, possiamo vedere alcune informazioni generali sulla VM. Successivamente, impariamo le dimensioni degli oggetti:

  • I riferimenti Java consumano 4 byte, i s / byte booleani sono 1 byte, i caratteri s / short s sono 2 byte, i s / float int sono 4 byte e, infine, s / double s lunghi sono 8 byte
  • Questi tipi consumano la stessa quantità di memoria anche quando li usiamo come elementi dell'array

Quindi, in presenza di riferimenti compressi, ogni valore booleano occupa 1 byte. Allo stesso modo, ogni booleano in un booleano [] consuma 1 byte. Tuttavia, i pad di allineamento e le intestazioni degli oggetti possono aumentare lo spazio occupato da boolean e boolean [] come vedremo più avanti.

3.1. Nessun riferimento compresso

Anche se disabilitiamo i riferimenti compressi tramite -XX: -UseCompressedOops , la dimensione booleana non cambierà affatto :

# 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]

D'altra parte, i riferimenti Java occupano il doppio della memoria.

Quindi, nonostante quello che potremmo aspettarci all'inizio, i booleani consumano 1 byte invece di solo 1 bit.

3.2. Word Tearing

Nella maggior parte delle architetture non è possibile accedere atomicamente a un singolo bit. Anche se volessimo farlo, probabilmente finiremmo per scrivere su bit adiacenti durante l'aggiornamento di un altro.

Uno degli obiettivi di progettazione della JVM è prevenire questo fenomeno, noto come word tearing . Cioè, nella JVM, ogni campo e ogni elemento dell'array dovrebbero essere distinti; gli aggiornamenti a un campo o elemento non devono interagire con letture o aggiornamenti di qualsiasi altro campo o elemento.

Per ricapitolare, i problemi di indirizzabilità e il word tearing sono i motivi principali per cui i booleani sono più di un singolo bit.

4. Puntatori a oggetti ordinari (OOP)

Ora che sappiamo che i booleani sono 1 byte, consideriamo questa semplice classe:

class BooleanWrapper { private boolean value; }

Se esaminiamo il layout di memoria di questa classe utilizzando JOL:

System.out.println(ClassLayout.parseClass(BooleanWrapper.class).toPrintable());

Quindi JOL stamperà il layout di memoria:

 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

Il layout BooleanWrapper è costituito da:

  • 12 byte per l'intestazione, comprese due parole di contrassegno e una parola klass . La JVM HotSpot utilizza la parola del marchio per memorizzare i metadati del GC, il codice hash dell'identità e le informazioni di blocco. Inoltre, utilizza la parola klass per memorizzare i metadati della classe come i controlli del tipo in runtime
  • 1 byte for the actual boolean value
  • 3 bytes of padding for alignment purposes

By default, object references should be aligned by 8 bytes. Therefore, the JVM adds 3 bytes to 13 bytes of header and boolean to make it 16 bytes.

Therefore, boolean fields may consume more memory because of their field alignment.

4.1. Custom Alignment

If we change the alignment value to 32 via -XX:ObjectAlignmentInBytes=32, then the same class layout changes to:

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

As shown above, the JVM adds 19 bytes of padding to make the object size a multiple of 32.

5. Array OOPs

Let's see how the JVM lays out a boolean array in memory:

boolean[] value = new boolean[3]; System.out.println(ClassLayout.parseInstance(value).toPrintable());

This will print the instance layout as following:

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. # [Z means boolean array 19 5 (loss due to the next object alignment)

In addition to two mark words and one klass word, array pointers contain an extra 4 bytes to store their lengths.

Since our array has three elements, the size of the array elements is 3 bytes. However, these 3 bytes will be padded by 5 field alignment bytes to ensure proper alignment.

Although each boolean element in an array is just 1 byte, the whole array consumes much more memory. In other words, we should consider the header and padding overhead while computing the array size.

6. Conclusion

In this quick tutorial, we saw that boolean fields are consuming 1 byte. Also, we learned that we should consider the header and padding overheads in object sizes.

Per una discussione più dettagliata, si consiglia vivamente di controllare la sezione oops del codice sorgente JVM. Inoltre, Aleksey Shipilëv ha un articolo molto più approfondito in questo settore.

Come al solito, tutti gli esempi sono disponibili su GitHub.