Qual è il consumo di memoria di un oggetto in Java?


216

Lo spazio di memoria consumato da un oggetto con 100 attributi è uguale a quello di 100 oggetti, con un attributo ciascuno?

Quanta memoria è allocata per un oggetto?
Quanto spazio aggiuntivo viene utilizzato quando si aggiunge un attributo?

Risposte:


180

Mindprod sottolinea che questa non è una domanda semplice a cui rispondere:

Una JVM è libera di archiviare i dati come preferisce internamente, big o little endian, con qualsiasi quantità di imbottitura o sovraccarico, anche se i primitivi devono comportarsi come se avessero le dimensioni ufficiali.
Ad esempio, il compilatore JVM o nativo potrebbe decidere di archiviare un boolean[]pezzo in blocchi lunghi a 64 bit come un BitSet. Non deve dirtelo, purché il programma fornisca le stesse risposte.

  • Potrebbe allocare alcuni oggetti temporanei nello stack.
  • Può ottimizzare alcune variabili o chiamate di metodi totalmente fuori dall'esistenza sostituendole con costanti.
  • Potrebbe eseguire metodi o cicli di versione, ovvero compilare due versioni di un metodo, ciascuna ottimizzata per una determinata situazione, quindi decidere in anticipo quale chiamare.

Quindi, ovviamente, l'hardware e il sistema operativo hanno cache multistrato, su chip-cache, cache SRAM, cache DRAM, set di lavoro RAM ordinario e archivio di backup su disco. I tuoi dati possono essere duplicati ad ogni livello di cache. Tutta questa complessità significa che è possibile prevedere solo approssimativamente il consumo di RAM.

Metodi di misurazione

È possibile utilizzare Instrumentation.getObjectSize()per ottenere una stima della memoria utilizzata da un oggetto.

Per visualizzare il layout dell'oggetto reale , l'impronta e i riferimenti, è possibile utilizzare lo strumento JOL (Java Object Layout) .

Intestazioni oggetto e riferimenti oggetto

In un moderno JDK a 64 bit, un oggetto ha un'intestazione di 12 byte, riempita con un multiplo di 8 byte, quindi la dimensione minima dell'oggetto è di 16 byte. Per le JVM a 32 bit, l'overhead è di 8 byte, riempito con un multiplo di 4 byte. (Dalla risposta di Dmitry Spikhalskiy , la risposta di Jayen , e JavaWorld .)

In genere, i riferimenti sono 4 byte su piattaforme a 32 bit o su piattaforme a 64 bit fino a -Xmx32G; e 8 byte sopra 32Gb ( -Xmx32G). (Vedi i riferimenti agli oggetti compressi .)

Di conseguenza, una JVM a 64 bit richiederebbe in genere il 30-50% di spazio in più sull'heap. ( Dovrei usare una JVM a 32 o 64 bit?, 2012, JDK 1.7)

Tipi, matrici e stringhe inscatolati

I wrapper in scatola hanno un sovraccarico rispetto ai tipi primitivi (da JavaWorld ):

  • Integer: Il risultato a 16 byte è leggermente peggiore di quanto mi aspettassi perché un intvalore può contenere solo 4 byte extra. L'utilizzo di un Integermi costa un sovraccarico di memoria del 300 percento rispetto a quando posso memorizzare il valore come tipo primitivo

  • Long: 16 byte anche: Chiaramente, le dimensioni effettive dell'oggetto sull'heap sono soggette ad un allineamento di memoria di basso livello fatto da una particolare implementazione JVM per un particolare tipo di CPU. Sembra che un Longoverhead sia 8 byte di Object, più altri 8 byte per il valore long effettivo. Al contrario, Integeraveva un foro di 4 byte inutilizzato, molto probabilmente perché la JVM che uso forza l'allineamento degli oggetti su un limite di parole di 8 byte.

Anche altri contenitori sono costosi:

  • Matrici multidimensionali : offre un'altra sorpresa.
    Gli sviluppatori utilizzano comunemente costrutti come int[dim1][dim2]nel calcolo numerico e scientifico.

    In int[dim1][dim2]un'istanza di array, ogni int[dim2]array nidificato è Objecta sé stante. Ciascuno aggiunge il solito overhead di array a 16 byte. Quando non ho bisogno di una matrice triangolare o sfilacciata, ciò rappresenta un sovraccarico puro. L'impatto aumenta quando le dimensioni dell'array differiscono notevolmente.

    Ad esempio, int[128][2]un'istanza richiede 3.600 byte. Rispetto ai 1.040 byte int[256]utilizzati da un'istanza (che ha la stessa capacità), 3.600 byte rappresentano un sovraccarico del 246 percento. Nel caso estremo di byte[256][1], il fattore ambientale è quasi 19! Confrontalo con la situazione C / C ++ in cui la stessa sintassi non aggiunge alcun sovraccarico di archiviazione.

  • String: Stringla crescita della memoria di a segue la crescita del suo array di caratteri interno. Tuttavia, la Stringclasse aggiunge altri 24 byte di sovraccarico.

    Per una Stringdimensione non vuota di 10 caratteri o meno, il costo aggiuntivo aggiunto relativo al carico utile utile (2 byte per ogni carattere più 4 byte per la lunghezza) varia dal 100 al 400 percento.

Allineamento

Considera questo oggetto di esempio :

class X {                      // 8 bytes for reference to the class definition
   int a;                      // 4 bytes
   byte b;                     // 1 byte
   Integer c = new Integer();  // 4 bytes for a reference
}

Una somma ingenua suggerirebbe che un'istanza di Xuserebbe 17 byte. Tuttavia, a causa dell'allineamento (chiamato anche padding), la JVM alloca la memoria in multipli di 8 byte, quindi invece di 17 byte allocare 24 byte.


int [128] [6]: 128 matrici di 6 in - 768 in totale, 3072 byte di dati + 2064 byte Overhead dell'oggetto = 5166 byte in totale. int [256]: 256 in totale - quindi non confrontabile. int [768]: 3072 byte di dati + 16 byte overhead - circa 3/5 dello spazio dell'array 2D - non abbastanza del 246% overhead!
JeeBee,

Ah, l'articolo originale usava int [128] [2] non int [128] [6] - mi chiedo come sia cambiato. Mostra anche che esempi estremi possono raccontare una storia diversa.
JeeBee,

2
Il sovraccarico è di 16 byte in JVM a 64 bit.
Tim Cooper,

3
@AlexWien: alcuni schemi di garbage collection potrebbero imporre una dimensione minima dell'oggetto separata dall'imbottitura. Durante la garbage collection, una volta che un oggetto viene copiato da una vecchia posizione a una nuova, la vecchia posizione potrebbe non dover più conservare i dati per l'oggetto, ma dovrà contenere un riferimento alla nuova posizione; potrebbe anche essere necessario memorizzare un riferimento alla vecchia posizione dell'oggetto in cui è stato scoperto il primo riferimento e l'offset di tale riferimento all'interno del vecchio oggetto [poiché il vecchio oggetto potrebbe ancora contenere riferimenti che non sono stati ancora elaborati].
supercat,

2
@AlexWien: L'uso della memoria nella posizione precedente di un oggetto per contenere le informazioni di contabilità del garbage collector evita la necessità di allocare altra memoria a tale scopo, ma può imporre una dimensione minima dell'oggetto che sia maggiore di quanto altrimenti richiesto. Penso che almeno una versione di .NET Garbage Collector utilizzi questo approccio; sarebbe certamente possibile che anche alcuni garbage collector di Java lo facessero.
Supercat,

34

Dipende dall'architettura / jdk. Per una moderna architettura JDK e 64 bit, un oggetto ha un'intestazione di 12 byte e un'imbottitura di 8 byte, quindi la dimensione minima dell'oggetto è di 16 byte. È possibile utilizzare uno strumento chiamato Java Object Layout per determinare una dimensione e ottenere dettagli sul layout degli oggetti e sulla struttura interna di qualsiasi entità o indovinare queste informazioni in base al riferimento di classe. Esempio di output per Integer nel mio ambiente:

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]

java.lang.Integer object internals:
 OFFSET  SIZE  TYPE DESCRIPTION                    VALUE
      0    12       (object header)                N/A
     12     4   int Integer.value                  N/A
Instance size: 16 bytes (estimated, the sample instance is not available)
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

Quindi, per Integer, la dimensione dell'istanza è di 16 byte, perché 4 byte int sono compattati in posizione subito dopo l'intestazione e prima del limite di riempimento.

Esempio di codice:

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.util.VMSupport;

public static void main(String[] args) {
    System.out.println(VMSupport.vmDetails());
    System.out.println(ClassLayout.parseClass(Integer.class).toPrintable());
}

Se usi Maven, per ottenere JOL:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.3.2</version>
</dependency>

28

Ogni oggetto ha un certo sovraccarico per il monitor associato e le informazioni sul tipo, nonché i campi stessi. Oltre a ciò, i campi possono essere disposti praticamente come la JVM ritiene opportuno (credo) - ma come mostrato in un'altra risposta , almeno alcune JVM comprenderanno abbastanza strettamente. Considera una classe come questa:

public class SingleByte
{
    private byte b;
}

vs

public class OneHundredBytes
{
    private byte b00, b01, ..., b99;
}

Su una JVM a 32 bit, mi aspetto che 100 istanze SingleByteprendano 1200 byte (8 byte di overhead + 4 byte per il campo a causa del riempimento / allineamento). Mi aspetterei che un'istanza OneHundredBytesoccupi 108 byte: l'overhead e quindi 100 byte, impacchettati. Tuttavia, può certamente variare a seconda della JVM: un'implementazione può decidere di non comprimere i campiOneHundredBytes , portando a 408 byte (= 8 byte in overhead + 4 * 100 byte allineati / riempiti). Su una JVM a 64 bit, anche l'overhead potrebbe essere più grande (non sicuro).

EDIT: vedi il commento qui sotto; a quanto pare HotSpot esegue il pad su limiti di 8 byte anziché 32, quindi ogni istanza di SingleByterichiedere 16 byte.

In entrambi i casi, il "singolo oggetto di grandi dimensioni" sarà almeno efficiente quanto più piccoli oggetti di piccole dimensioni - per casi semplici come questo.


9
In realtà, un'istanza di SingleByte richiederebbe 16 byte su una Sun JVM, ovvero 8 byte in testa, 4 byte per il campo e quindi 4 byte per il riempimento degli oggetti, poiché il compilatore HotSpot arrotonda tutto a multipli di 8.
Paul Wagland

6

La memoria totale utilizzata / libera di un programma può essere ottenuta nel programma tramite

java.lang.Runtime.getRuntime();

Il runtime ha diversi metodi che si riferiscono alla memoria. Il seguente esempio di codifica ne dimostra l'utilizzo.

package test;

 import java.util.ArrayList;
 import java.util.List;

 public class PerformanceTest {
     private static final long MEGABYTE = 1024L * 1024L;

     public static long bytesToMegabytes(long bytes) {
         return bytes / MEGABYTE;
     }

     public static void main(String[] args) {
         // I assume you will know how to create a object Person yourself...
         List < Person > list = new ArrayList < Person > ();
         for (int i = 0; i <= 100000; i++) {
             list.add(new Person("Jim", "Knopf"));
         }
         // Get the Java runtime
         Runtime runtime = Runtime.getRuntime();
         // Run the garbage collector
         runtime.gc();
         // Calculate the used memory
         long memory = runtime.totalMemory() - runtime.freeMemory();
         System.out.println("Used memory is bytes: " + memory);
         System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory));
     }
 }

6

Sembra che ogni oggetto abbia un sovraccarico di 16 byte su sistemi a 32 bit (e 24 byte su sistemi a 64 bit).

http://algs4.cs.princeton.edu/14analysis/ è una buona fonte di informazioni. Un esempio tra molti buoni è il seguente.

inserisci qui la descrizione dell'immagine

http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf è anche molto istruttivo, ad esempio:

inserisci qui la descrizione dell'immagine


"Sembra che ogni oggetto abbia un sovraccarico di 16 byte su sistemi a 32 bit (e 24 byte su sistemi a 64 bit)." Non è corretto, almeno per gli attuali JDK. Dai un'occhiata alla mia risposta per esempio intero. Il sovraccarico dell'oggetto è di almeno 12 byte per l'intestazione per il sistema a 64 bit e il JDK moderno. Può essere più a causa del riempimento, dipende dal layout effettivo dei campi nell'oggetto.
Dmitry Spikhalskiy,

Il secondo collegamento all'esercitazione Java efficiente in termini di memoria sembra essere morto: ottengo "Proibito".
tsleyson,

6

Lo spazio di memoria consumato da un oggetto con 100 attributi è uguale a quello di 100 oggetti, con un attributo ciascuno?

No.

Quanta memoria è allocata per un oggetto?

  • L'overhead è di 8 byte su 32 bit, 12 byte su 64 bit; e quindi arrotondato per eccesso a un multiplo di 4 byte (32 bit) o ​​8 byte (64 bit).

Quanto spazio aggiuntivo viene utilizzato quando si aggiunge un attributo?

  • Attributi vanno da 1 byte (byte) a 8 byte (/ long double), ma i riferimenti sono o 4 byte o 8 byte seconda non che si tratti di 32 bit o 64 bit, ma piuttosto se -Xmx è <32 Gb o> = 32 Gb: tipico 64 -bit Le JVM hanno un'ottimizzazione chiamata "-UseCompressedOops" che comprime i riferimenti a 4 byte se l'heap è inferiore a 32Gb.

1
un carattere è 16 bit, non 8 bit.
comonad,

hai ragione. qualcuno sembra aver modificato la mia risposta originale
Jayen,

5

No, anche la registrazione di un oggetto richiede un po 'di memoria. 100 oggetti con 1 attributo occuperanno più memoria.


4

La domanda sarà molto ampia.

Dipende dalla variabile di classe o è possibile chiamare come stati l'utilizzo della memoria in java.

Ha anche alcuni requisiti di memoria aggiuntivi per intestazioni e riferimenti.

Include la memoria heap utilizzata da un oggetto Java

  • memoria per campi primitivi, in base alla loro dimensione (vedi sotto per le dimensioni dei tipi primitivi);

  • memoria per campi di riferimento (4 byte ciascuno);

  • un'intestazione di oggetto, composta da pochi byte di informazioni di "pulizia";

Gli oggetti in Java richiedono anche alcune informazioni di "pulizia", ​​come la registrazione della classe di un oggetto, ID e flag di stato, ad esempio se l'oggetto è attualmente raggiungibile, attualmente bloccato dalla sincronizzazione, ecc.

Le dimensioni dell'intestazione dell'oggetto Java variano su jvm a 32 e 64 bit.

Sebbene questi siano i principali utenti della memoria, jvm richiede anche campi aggiuntivi, a volte come per l'allineamento del codice, ecc

Dimensioni di tipi primitivi

booleano e byte - 1

char & short - 2

int & float - 4

lungo e doppio - 8


I lettori possono anche trovare questo documento molto illuminante: cs.virginia.edu/kim/publicity/pldi09tutorials/…
quellish



1

no, 100 piccoli oggetti richiedono più informazioni (memoria) di un grande.


0

Le regole sulla quantità di memoria consumata dipendono dall'implementazione della JVM e dall'architettura della CPU (ad esempio 32 bit contro 64 bit).

Per le regole dettagliate per il SUN JVM controlla il mio vecchio blog

Saluti, Markus


Sono abbastanza sicuro che Sun Java 1.6 a 64 bit abbia bisogno di 12 byte per un oggetto semplice + 4 di riempimento = 16; un oggetto + un campo intero = 12 + 4 = 16
AlexWien

Hai chiuso il tuo blog?
Johan Boulé,

Non proprio, non sono sicuro che i blog di SAP si siano in qualche modo spostati. La maggior parte può essere trovata qui kohlerm.blogspot.com
kohlerm l'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.