Antworten:
Mindprod weist darauf hin, dass dies keine einfache Frage ist:
Eine JVM kann Daten nach Belieben intern, groß oder klein, mit beliebiger Menge an Auffüllung oder Aufwand speichern, obwohl sich Grundelemente so verhalten müssen, als hätten sie die offiziellen Größen.
Beispielsweise kann die JVM oder der native Compiler entscheiden, einenboolean[]
in 64 Bit langen Blöcken wie a zu speichernBitSet
. Es muss Ihnen nicht sagen, solange das Programm die gleichen Antworten gibt.
- Möglicherweise werden einige temporäre Objekte auf dem Stapel zugewiesen.
- Es kann einige Variablen oder Methodenaufrufe optimieren, die vollständig nicht mehr existieren, und sie durch Konstanten ersetzen.
- Es kann Methoden oder Schleifen versionieren, dh zwei Versionen einer Methode kompilieren, die jeweils für eine bestimmte Situation optimiert sind, und dann im Voraus entscheiden, welche aufgerufen werden soll.
Dann haben die Hardware und das Betriebssystem natürlich mehrschichtige Caches, einen Chip-Cache, einen SRAM-Cache, einen DRAM-Cache, einen normalen RAM-Arbeitssatz und einen Sicherungsspeicher auf der Festplatte. Ihre Daten können auf jeder Cache-Ebene dupliziert werden. All diese Komplexität bedeutet, dass Sie den RAM-Verbrauch nur sehr grob vorhersagen können.
Sie können verwenden Instrumentation.getObjectSize()
, um eine Schätzung des von einem Objekt verbrauchten Speichers zu erhalten.
Um das tatsächliche Objektlayout, den Footprint und die Referenzen zu visualisieren , können Sie das JOL-Tool (Java Object Layout) verwenden .
In einem modernen 64-Bit-JDK verfügt ein Objekt über einen 12-Byte-Header, der mit einem Vielfachen von 8 Byte aufgefüllt ist, sodass die minimale Objektgröße 16 Byte beträgt. Bei 32-Bit-JVMs beträgt der Overhead 8 Byte, aufgefüllt mit einem Vielfachen von 4 Byte. (Von Dmitry Spikhalskiy Antwort , Jayen Antwort und Javaworld .)
In der Regel sind Referenzen 4 Byte auf 32-Bit-Plattformen oder auf 64-Bit-Plattformen bis zu -Xmx32G
; und 8 Bytes über 32 GB ( -Xmx32G
). (Siehe komprimierte Objektreferenzen .)
Infolgedessen benötigt eine 64-Bit-JVM normalerweise 30-50% mehr Heap-Speicherplatz. ( Soll ich eine 32- oder 64-Bit-JVM verwenden ? , 2012, JDK 1.7)
Boxed Wrapper haben im Vergleich zu primitiven Typen (aus JavaWorld ) Overhead :
Integer
: Das 16-Byte-Ergebnis ist etwas schlechter als erwartet, da einint
Wert in nur 4 zusätzliche Bytes passt. Die Verwendung von aInteger
kostet mich 300 Prozent Speicheraufwand im Vergleich dazu, wenn ich den Wert als primitiven Typ speichern kann
Long
: 16 Bytes auch: Die tatsächliche Objektgröße auf dem Heap unterliegt eindeutig einer Speicherausrichtung auf niedriger Ebene, die von einer bestimmten JVM-Implementierung für einen bestimmten CPU-Typ durchgeführt wird. Es sieht so aus, als ob aLong
8 Byte Objekt-Overhead plus 8 Byte mehr für den tatsächlichen Long-Wert beträgt. Im Gegensatz dazuInteger
hatte ein nicht verwendetes 4-Byte-Loch, höchstwahrscheinlich, weil die JVM, die ich verwende, die Objektausrichtung an einer 8-Byte-Wortgrenze erzwingt.
Andere Container sind ebenfalls teuer:
Mehrdimensionale Arrays : Es bietet eine weitere Überraschung.
Entwickler verwenden üblicherweise Konstrukte wieint[dim1][dim2]
im numerischen und wissenschaftlichen Rechnen.In einer
int[dim1][dim2]
Array-Instanz ist jedes verschachtelteint[dim2]
Array einObject
eigenständiges Array . Jeder fügt den üblichen 16-Byte-Array-Overhead hinzu. Wenn ich kein dreieckiges oder zerlumptes Array benötige, bedeutet das reinen Overhead. Die Auswirkung nimmt zu, wenn sich die Array-Abmessungen stark unterscheiden.Eine
int[128][2]
Instanz benötigt beispielsweise 3.600 Bytes. Im Vergleich zu den 1.040 Bytes, die eineint[256]
Instanz verwendet (mit derselben Kapazität), bedeuten 3.600 Bytes einen Overhead von 246 Prozent. Im Extremfallbyte[256][1]
beträgt der Overhead-Faktor fast 19! Vergleichen Sie dies mit der C / C ++ - Situation, in der dieselbe Syntax keinen Speicheraufwand verursacht.
String
:String
Das Speicherwachstum von a verfolgt das Wachstum seines internen Char-Arrays. DieString
Klasse fügt jedoch weitere 24 Byte Overhead hinzu.Bei einer Nicht-Leere mit
String
einer Größe von 10 Zeichen oder weniger liegen die zusätzlichen Gemeinkosten im Verhältnis zur Nutzlast (2 Byte für jedes Zeichen plus 4 Byte für die Länge) zwischen 100 und 400 Prozent.
Betrachten Sie dieses Beispielobjekt :
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
}
Eine naive Summe würde bedeuten, dass eine Instanz von X
17 Bytes verwenden würde. Aufgrund der Ausrichtung (auch als Auffüllen bezeichnet) weist die JVM den Speicher jedoch in Vielfachen von 8 Bytes zu, sodass anstelle von 17 Bytes 24 Bytes zugewiesen werden.
Das hängt von der Architektur / jdk ab. Bei einer modernen JDK- und 64-Bit-Architektur verfügt ein Objekt über einen 12-Byte-Header und einen Abstand von 8 Byte. Die minimale Objektgröße beträgt also 16 Byte. Sie können ein Tool namens Java Object Layout verwenden , um eine Größe zu bestimmen und Details zum Objektlayout und zur internen Struktur einer Entität abzurufen oder diese Informationen anhand einer Klassenreferenz zu erraten. Beispiel für eine Ausgabe für Integer in meiner Umgebung:
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
Für Integer beträgt die Instanzgröße also 16 Byte, da 4 Byte int direkt nach dem Header und vor dem Auffüllen der Grenze komprimiert werden.
Codebeispiel:
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());
}
Wenn Sie Maven verwenden, um JOL zu erhalten:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.3.2</version>
</dependency>
Jedes Objekt hat einen bestimmten Overhead für die zugehörigen Monitor- und Typinformationen sowie für die Felder selbst. Darüber hinaus können Felder so ziemlich angelegt werden, wie es die JVM für richtig hält (glaube ich) - aber wie in einer anderen Antwort gezeigt , werden zumindest einige JVMs ziemlich dicht gepackt sein. Stellen Sie sich eine Klasse wie diese vor:
public class SingleByte
{
private byte b;
}
vs.
public class OneHundredBytes
{
private byte b00, b01, ..., b99;
}
Bei einer 32-Bit-JVM würde ich erwarten, dass 100 Instanzen SingleByte
1200 Byte benötigen (8 Byte Overhead + 4 Byte für das Feld aufgrund von Auffüllen / Ausrichtung). Ich würde erwarten, dass eine Instanz OneHundredBytes
108 Bytes benötigt - der Overhead und dann 100 Bytes, gepackt. Es kann jedoch je nach JVM variieren - eine Implementierung kann entscheiden, die Felder nicht einzupacken OneHundredBytes
, was dazu führt, dass 408 Bytes benötigt werden (= 8 Bytes Overhead + 4 * 100 ausgerichtete / aufgefüllte Bytes). Bei einer 64-Bit-JVM ist der Overhead möglicherweise auch größer (nicht sicher).
BEARBEITEN: Siehe den Kommentar unten; Anscheinend werden HotSpot-Pads an 8-Byte-Grenzen anstatt an 32 angehängt, sodass jede Instanz von SingleByte
16 Bytes benötigt.
In jedem Fall ist das "einzelne große Objekt" mindestens so effizient wie mehrere kleine Objekte - für einfache Fälle wie diesen.
Der insgesamt genutzte / freie Speicher eines Programms kann im Programm über abgerufen werden
java.lang.Runtime.getRuntime();
Die Laufzeit hat mehrere Methoden, die sich auf den Speicher beziehen. Das folgende Codierungsbeispiel zeigt die Verwendung.
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));
}
}
Es scheint, dass jedes Objekt auf 32-Bit-Systemen einen Overhead von 16 Byte hat (und auf 64-Bit-Systemen 24 Byte).
http://algs4.cs.princeton.edu/14analysis/ ist eine gute Informationsquelle. Ein Beispiel unter vielen guten ist das folgende.
http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf ist auch sehr informativ, zum Beispiel:
Ist der von einem Objekt mit 100 Attributen belegte Speicherplatz der gleiche wie der von 100 Objekten mit jeweils einem Attribut?
Nein.
Wie viel Speicher ist für ein Objekt reserviert?
Wie viel zusätzlicher Speicherplatz wird beim Hinzufügen eines Attributs verwendet?
Die Frage wird sehr weit gefasst sein.
Dies hängt von der Klassenvariablen ab, oder Sie können die Speichernutzung in Java als Status aufrufen.
Es hat auch einige zusätzliche Speicheranforderungen für Header und Referenzierungen.
Der von einem Java-Objekt verwendete Heap-Speicher enthält
Speicher für primitive Felder entsprechend ihrer Größe (siehe unten für Größen primitiver Typen);
Speicher für Referenzfelder (jeweils 4 Bytes);
einen Objektheader, der aus einigen Bytes "Housekeeping" -Informationen besteht;
Objekte in Java erfordern auch einige "Housekeeping" -Informationen, z. B. das Aufzeichnen der Klassen-, ID- und Statusflags eines Objekts, z. B. ob das Objekt derzeit erreichbar ist, derzeit synchronisiert ist usw.
Die Größe des Java-Objekt-Headers variiert je nach 32- und 64-Bit-JVM.
Obwohl dies die Hauptspeicherkonsumenten sind, benötigt jvm manchmal auch zusätzliche Felder, wie zum Beispiel für die Ausrichtung des Codes usw.
Größen primitiver Typen
Boolescher Wert & Byte - 1
char & short - 2
int & float - 4
lang & doppelt - 8
Ich habe sehr gute Ergebnisse mit dem in einer anderen Antwort erwähnten Ansatz java.lang.instrument.Instrumentation erzielt . Gute Beispiele für seine Verwendung finden Sie im Eintrag Instrumentation Memory Counter aus dem JavaSpecialists 'Newsletter und in der Bibliothek java.sizeOf auf SourceForge.
Falls es für jemanden nützlich ist, können Sie einen kleinen Java-Agenten von meiner Website herunterladen , um die Speichernutzung eines Objekts abzufragen . Damit können Sie auch die "tiefe" Speichernutzung abfragen.
(String, Integer)
Guava-Cache pro Element zu erhalten. Vielen Dank!
Die Regeln für den Speicherbedarf hängen von der JVM-Implementierung und der CPU-Architektur ab (z. B. 32 Bit gegenüber 64 Bit).
Die detaillierten Regeln für die SUN JVM finden Sie in meinem alten Blog
Grüße, Markus