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é unintvalore può contenere solo 4 byte extra. L'utilizzo di unIntegermi 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 unLongoverhead 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 comeint[dim1][dim2]nel calcolo numerico e scientifico.In
int[dim1][dim2]un'istanza di array, ogniint[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 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:Stringla crescita della memoria di a segue la crescita del suo array di caratteri interno. Tuttavia, laStringclasse 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.
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.
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 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.
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