Wie hoch ist der Speicherverbrauch eines Objekts in Java?

216

Ist der von einem Objekt mit 100 Attributen belegte Speicherplatz der gleiche wie der von 100 Objekten mit jeweils einem Attribut?

Wie viel Speicher ist für ein Objekt reserviert?
Wie viel zusätzlicher Speicherplatz wird beim Hinzufügen eines Attributs verwendet?

Keparo
quelle

Antworten:

180

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, einen boolean[]in 64 Bit langen Blöcken wie a zu speichern BitSet. 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.

Messmethoden

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 .

Objektheader und Objektreferenzen

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-Typen, Arrays und Strings

Boxed Wrapper haben im Vergleich zu primitiven Typen (aus JavaWorld ) Overhead :

  • Integer: Das 16-Byte-Ergebnis ist etwas schlechter als erwartet, da ein intWert in nur 4 zusätzliche Bytes passt. Die Verwendung von a Integerkostet 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 a Long8 Byte Objekt-Overhead plus 8 Byte mehr für den tatsächlichen Long-Wert beträgt. Im Gegensatz dazu Integerhatte 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 wie int[dim1][dim2]im numerischen und wissenschaftlichen Rechnen.

    In einer int[dim1][dim2]Array-Instanz ist jedes verschachtelte int[dim2]Array ein Objecteigenstä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 eine int[256]Instanz verwendet (mit derselben Kapazität), bedeuten 3.600 Bytes einen Overhead von 246 Prozent. Im Extremfall byte[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: StringDas Speicherwachstum von a verfolgt das Wachstum seines internen Char-Arrays. Die StringKlasse fügt jedoch weitere 24 Byte Overhead hinzu.

    Bei einer Nicht-Leere mit Stringeiner 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.

Ausrichtung

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 X17 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.

VonC
quelle
int [128] [6]: 128 Arrays mit 6 Zoll - insgesamt 768 Zoll, 3072 Datenbytes + 2064 Byte Objekt-Overhead = insgesamt 5166 Byte. int [256]: 256 256 insgesamt - daher nicht vergleichbar. int [768]: 3072 Datenbytes + 16 Bytes Overhead - ungefähr 3/5 des Raums des 2D-Arrays - nicht ganz 246% Overhead!
JeeBee
Ah, der ursprüngliche Artikel verwendete int [128] [2], nicht int [128] [6] - frage mich, wie sich das geändert hat. Zeigt auch, dass extreme Beispiele eine andere Geschichte erzählen können.
JeeBee
2
Der Overhead in 64-Bit-JVMs beträgt 16 Byte.
Tim Cooper
3
@AlexWien: Einige Garbage-Collection-Schemata legen möglicherweise eine minimale Objektgröße fest, die vom Auffüllen getrennt ist. Wenn ein Objekt während der Speicherbereinigung von einem alten Speicherort an einen neuen kopiert wird, muss der alte Speicherort möglicherweise nicht mehr die Daten für das Objekt enthalten, sondern muss einen Verweis auf den neuen Speicherort enthalten. Möglicherweise muss auch eine Referenz auf den alten Speicherort des Objekts, an dem die erste Referenz entdeckt wurde, und der Versatz dieser Referenz innerhalb des alten Objekts gespeichert werden [da das alte Objekt möglicherweise noch Referenzen enthält, die noch nicht verarbeitet wurden].
Supercat
2
@AlexWien: Durch die Verwendung des Speichers am alten Speicherort eines Objekts zum Speichern der Buchhaltungsinformationen des Garbage Collectors wird die Notwendigkeit vermieden, anderen Speicher für diesen Zweck zuzuweisen, es kann jedoch eine Mindestobjektgröße erforderlich sein, die größer ist, als dies sonst erforderlich wäre. Ich denke, mindestens eine Version von .NET Garbage Collector verwendet diesen Ansatz. Einige Java-Garbage-Collectors könnten dies sicherlich auch tun.
Supercat
34

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>
Dmitry Spikhalskiy
quelle
28

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 SingleByte1200 Byte benötigen (8 Byte Overhead + 4 Byte für das Feld aufgrund von Auffüllen / Ausrichtung). Ich würde erwarten, dass eine Instanz OneHundredBytes108 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 SingleByte16 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.

Jon Skeet
quelle
9
Tatsächlich würde eine Instanz von SingleByte 16 Bytes auf einer Sun-JVM benötigen, dh 8 Bytes Overhead, 4 Bytes für das Feld und dann 4 Bytes für das Auffüllen von Objekten, da der HotSpot-Compiler alles auf ein Vielfaches von 8
rundet
6

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));
     }
 }
nazar_art
quelle
6

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.

Geben Sie hier die Bildbeschreibung ein

http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf ist auch sehr informativ, zum Beispiel:

Geben Sie hier die Bildbeschreibung ein

Arun
quelle
"Es scheint, dass jedes Objekt auf 32-Bit-Systemen einen Overhead von 16 Byte hat (und auf 64-Bit-Systemen 24 Byte)." Zumindest für aktuelle JDKs ist dies nicht korrekt. Schauen Sie sich meine Antwort für das Integer-Beispiel an. Der Overhead des Objekts beträgt mindestens 12 Byte für den Header des 64-Bit-Systems und des modernen JDK. Kann aufgrund des Auffüllens mehr sein, hängt vom tatsächlichen Layout der Felder im Objekt ab.
Dmitry Spikhalskiy
Der zweite Link zum speichereffizienten Java-Tutorial scheint tot zu sein - ich bekomme "Verboten".
Tsleyson
6

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?

  • Der Overhead beträgt 8 Byte bei 32-Bit, 12 Byte bei 64-Bit; und dann auf ein Vielfaches von 4 Bytes (32 Bit) oder 8 Bytes (64 Bit) aufgerundet.

Wie viel zusätzlicher Speicherplatz wird beim Hinzufügen eines Attributs verwendet?

  • Attribute reichen von 1 (Byte) bis 8 Byte (long / double), aber Referenzen sind entweder 4 Byte oder 8 Byte je nicht , ob es die 32 - Bit- oder 64 - Bit, sondern vielmehr , ob -Xmx ist <32Gb oder> = 32 GB: typisch 64 -bit JVMs haben eine Optimierung namens "-UseCompressedOops", die Verweise auf 4 Bytes komprimiert, wenn der Heap unter 32 GB liegt.
Jayen
quelle
1
Ein Zeichen ist 16 Bit, nicht 8 Bit.
Comonad
Du hast Recht. jemand scheint meine ursprüngliche Antwort bearbeitet zu haben
Jayen
5

Nein, das Registrieren eines Objekts benötigt ebenfalls etwas Speicher. 100 Objekte mit 1 Attribut belegen mehr Speicher.

Mendelt
quelle
4

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

Nikhil Agrawal
quelle
Leser können dieses Papier auch sehr aufschlussreich
quellish
2

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.

Neil Coffey
quelle
Dies funktionierte hervorragend, um eine grobe Schätzung des Speicherbedarfs eines (String, Integer)Guava-Cache pro Element zu erhalten. Vielen Dank!
Steve K
1

Nein, 100 kleine Objekte benötigen mehr Informationen (Speicher) als ein großes.

Burkhard
quelle
0

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

kohlerm
quelle
Ich bin mir ziemlich sicher, dass Sun Java 1.6 64bit 12 Bytes für ein einfaches Objekt benötigt + 4 Auffüllung = 16; ein Objekt + ein ganzzahliges Feld = 12 + 4 = 16
AlexWien
Hast du dein Blog heruntergefahren?
Johan Boulé
Nicht wirklich, nicht sicher, ob sich die SAP-Blogs irgendwie bewegt haben. Das meiste davon finden Sie hier kohlerm.blogspot.com
kohlerm