Risposte:
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 unboolean[]
pezzo in blocchi lunghi a 64 bit come unBitSet
. 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.
È 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) .
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)
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é unint
valore può contenere solo 4 byte extra. L'utilizzo di unInteger
mi 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 unLong
overhead sia 8 byte di Object, più altri 8 byte per il valore long effettivo. Al contrario,Integer
aveva 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 comeint[dim1][dim2]
nel calcolo numerico e scientifico.In
int[dim1][dim2]
un'istanza di array, ogniint[dim2]
array nidificato èObject
a 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 byteint[256]
utilizzati da un'istanza (che ha la stessa capacità), 3.600 byte rappresentano un sovraccarico del 246 percento. Nel caso estremo dibyte[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
:String
la crescita della memoria di a segue la crescita del suo array di caratteri interno. Tuttavia, laString
classe aggiunge altri 24 byte di sovraccarico.Per una
String
dimensione 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.
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 X
userebbe 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.
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>
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 SingleByte
prendano 1200 byte (8 byte di overhead + 4 byte per il campo a causa del riempimento / allineamento). Mi aspetterei che un'istanza OneHundredBytes
occupi 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 SingleByte
richiedere 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.
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));
}
}
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.
http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf è anche molto istruttivo, ad esempio:
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?
Quanto spazio aggiuntivo viene utilizzato quando si aggiunge un attributo?
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
Ho ottenuto ottimi risultati dall'approccio java.lang.instrument.Instrumentation menzionato in un'altra risposta. Per buoni esempi del suo utilizzo, consultare la voce, Contatore di memoria della strumentazione dalla newsletter di JavaSpecialists e la libreria java.sizeOf su SourceForge.
Nel caso sia utile a chiunque, è possibile scaricare dal mio sito Web un piccolo agente Java per interrogare l'utilizzo della memoria di un oggetto . Ti consentirà anche di interrogare l'utilizzo della memoria "in profondità".
(String, Integer)
Guava Cache, per elemento. Grazie!
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