Objektgrößenunterschied in 32-Bit- und 64-Bit-Systemen in Java

8

Ich bin auf eine Interviewfrage gestoßen:

%Vor%

Wie viel Speicherobjekt dieser Klasse braucht und warum bei Implementierung auf:

a) 32-Bit-Computer

b) 64-Bit-Computer

Ich habe die Antwort als:

%Vor%

Wegen zusätzlichem Speicher reserviert, der 8 Byte in 32-Bit-System und 16 Byte in 64-Bit-System zusätzlich zu der normalen Größe des Objekts ist.

Können Sie bitte eine Erklärung für diese Aussage geben?

PS .: Ich möchte auch eine Antwort teilen, die ich von einer anderen Quelle bekommen habe (kann sich nicht darauf verlassen, wollte bestätigen):

In 32 Bit PC-Objekt belegt 8 Bytes mehr als die tatsächliche Obj Größe wie von seinen Datenelementen definiert ..... und in 64 Bit PC-Objekt belegt 16 Bytes mehr als die tatsächliche Objektgröße wie von seinen Daten Mitgliedern definiert. .. nun kommt die Frage auf, warum es passiert ... der Grund dafür ist, soweit ich weiß, ::: in 32 bit pc die ersten 8 Bytes sind reserviert von JVM für die Bezugnahme auf Klassendefinitionen, Ausrichtung, Leerzeichen für Superklasse und Sub classes etc .. und für 64 bit reserviert es 16 Bytes für diese .. Es gibt eine Formel für die Berechnung, wie viel Heap-Speicherplatz von einem Objekt benötigt wird, wenn es erstellt wird und es als ..... berechnet wird. . Shallow Heap Size = [Verweis auf die Klassendefinition] + Leerzeichen für Superklassenfelder + Leerzeichen für Instanzfelder + [Ausrichtung]

Wie berechnen Sie, wie viel Speicher das Objekt "selbst" benötigt? Anscheinend gibt es eine Formel dafür:

Shallow Heap Size = [Verweis auf die Klassendefinition] + Leerzeichen für Superklassenfelder + Leerzeichen für Instanzfelder + [Ausrichtung]

Scheint nicht so hilfreich, oder? Lassen Sie uns versuchen, die Formel mit dem folgenden Beispielcode anzuwenden:

%Vor%

Nun, die Frage, die wir beantworten wollen, lautet: Wie viel flache Heap-Größe benötigt eine Instanz eines Y? Beginnen wir mit der Berechnung, vorausgesetzt wir befinden uns in einer 32-Bit-x86-Architektur:

Als Ausgangspunkt - Y ist eine Unterklasse von X, also enthält seine Größe "etwas" von der Superklasse. Bevor wir also die Größe von Y berechnen, untersuchen wir die flache Größe von X.

Wenn Sie in die Berechnungen von X springen, werden die ersten 8 Bytes verwendet, um auf seine Klassendefinition zu verweisen. Diese Referenz ist immer in allen Java-Objekten vorhanden und wird von JVM verwendet, um das Speicherlayout des folgenden Zustands zu definieren. Es hat auch drei Instanzvariablen - ein int, ein Integer und ein Byte. Diese Instanzvariablen erfordern einen Heap wie folgt:

ein Byte ist, was es sein soll. 1 Byte in einem Speicher. Ein int in unserer 32-Bit-Architektur benötigt 4 Bytes. ein Verweis auf den Integer benötigt ebenfalls 4 Bytes. Beachten Sie, dass wir bei der Berechnung von beibehaltenem Heap auch die Größe eines in das Integer-Objekt eingepackten Primitivs berücksichtigen sollten. Da wir hier jedoch einen flachen Heap berechnen, verwenden wir in unseren Berechnungen nur die Referenzgröße von 4 Byte. Also - ist es das? Shallow Heap von X = 8 Bytes aus dem Verweis auf die Klassendefinition + 1 Byte (das Byte) + 4 Bytes (der Int) + 4 Bytes (Verweis auf die Ganzzahl) = 17 Bytes? In der Tat - nein. Was jetzt ins Spiel kommt, nennt man Ausrichtung (auch Padding genannt). Es bedeutet, dass die JVM den Speicher in Vielfachen von 8 Bytes zuweist, also würden wir statt 17 Bytes 24 Bytes zuweisen, wenn wir eine Instanz von X erstellen würden.

Wenn du uns bis hierhin folgen könntest, gut, aber jetzt versuchen wir, die Dinge noch komplexer zu machen. Wir erstellen KEINE Instanz von X, sondern eine Instanz von Y. Was das bedeutet ist, dass wir die 8 Bytes von der Referenz auf die Klassendefinition und die Ausrichtung abziehen können. Es ist vielleicht nicht zu offensichtlich, aber - haben Sie bemerkt, dass wir bei der Berechnung der flachen Größe von X nicht berücksichtigt haben, dass es auch java.lang.Object erweitert, wie alle Klassen es tun, auch wenn Sie es nicht explizit angeben Dein Quellcode? Wir müssen die Header-Größen von Superklassen nicht berücksichtigen, weil JVM schlau genug ist, es anhand der Klassendefinitionen selbst zu überprüfen, anstatt es ständig in die Objektheader zu kopieren.

Gleiches gilt für die Ausrichtung - wenn Sie ein Objekt erstellen, richten Sie es nur einmal aus, nicht an den Grenzen der Oberklassen- / Unterklassendefinitionen. Wir können also sicher sagen, dass Sie beim Erstellen einer Unterklasse für X nur 9 Bytes von den Instanzvariablen erben.

Schließlich können wir zu der anfänglichen Aufgabe springen und anfangen, die Größe von Y zu berechnen. Wie wir gesehen haben, haben wir bereits 9 Bytes an die Superklassenfelder verloren. Mal sehen, was hinzugefügt wird, wenn wir tatsächlich eine Instanz von Y konstruieren.

Y-Header, die auf seine Klassendefinition verweisen, verbrauchen 8 Bytes. Das Gleiche wie bei früheren. Das Datum ist eine Referenz auf ein Objekt. 4 Bytes. Einfach. Die Liste ist eine Referenz auf eine Sammlung. Wieder 4 Bytes. Trivial. Zusätzlich zu den 9 Bytes der Superklasse haben wir 8 Bytes aus dem Header, 2 × 4 Bytes aus den beiden Referenzen (die Liste und das Datum). Die gesamte flache Größe für die Instanz von Y wäre 25 Bytes, die auf 32 ausgerichtet werden.

    
Himanshu Aggarwal 19.07.2014, 04:48
quelle

4 Antworten

9

Hängt von JVM ab. In Bezug auf HotSpot JVM lautet die richtige Antwort:

  • 32-Bit-JVM: 24 Byte = align8 (4 Byte mark_word + 4 Byte Klassenreferenz + 4 + 4 + 2 Byte Felddaten)
  • 64-Bit-JVM -XX: + UseCompressedOops: 24 Byte = align8 (8 Byte Mark_Word + 4 Byte Klassenreferenz + 4 + 4 + 2 Byte Felddaten)
  • 64-Bit-JVM -XX: -UseCompressedOops: 32 Byte = align8 (8 Byte Mark_Word + 8 Byte Klassenreferenz + 4 + 4 + 2 Byte Felddaten)
apangin 19.07.2014 15:20
quelle
5

Ich würde ein solches Interview Frage wie folgt beantworten:

  • Es ist nicht angegeben ...

  • Es hängt möglicherweise von der Java-Implementierung (d. h. der Java-Version, dem Anbieter und dem Ziel-Befehlssatz / der Zielarchitektur) sowie von 32 und 64 Bit ab.

  • Das Objekt besteht aus den Feldern (deren Ausrichtung plattformspezifisch ist) mit einem Objektheader (dessen Größe plattformspezifisch ist), und die Größe wird dann auf eine plattformspezifische Heap-Objektgranularität aufgerundet.

    >
  • Ein besserer Ansatz besteht darin, die Objektgröße zu messen (z. B. mithilfe von TLAB in einer modernen HotSpot-JVM).

  • Während die Objektgrößen variieren können, gilt die Bedeutung von int und char nicht. Ein int ist immer 32 Bit und ein char ist immer 16 Bit ohne Vorzeichen.

Wenn der Interviewer Ihnen das gesagt hat:

  

Für 32-Bit: 4 + 4 + 2 + 8 = 18 Bytes

Das bedeutet wahrscheinlich eine int und int und eine char plus 2 32-Bit Wörter des Objektheaders. Ich denke jedoch, dass er falsch liegt. Tatsächlich sollte 2 wahrscheinlich ein 4 sein, weil ich glaube, dass Felder typischerweise als 4 (oder 8) Bytes gespeichert sind, die an einer 32-Bit-Wortgrenze ausgerichtet sind

  

Für 64-Bit: 4 + 4 + 2 + 16 = 26 Bytes

Wie oben, aber mit 2 64-Bit-Wörtern des Objektheaders. Auch das halte ich nicht für richtig.

  

Wegen zusätzlichem Speicher reserviert, der 8 Byte in 32-Bit-System und 16 Byte in 64-Bit-System zusätzlich zu der normalen Größe des Objekts ist.

Die Granularität der Heap-Zuweisung besteht aus mehreren zwei Wörtern.

Aber ich möchte betonen, dass dies nicht unbedingt richtig ist. Sicherlich verlangt nichts in den offiziellen JVM-Spezifikationen, dass Objekte so dargestellt werden.

    
Stephen C 19.07.2014 05:38
quelle
2

Um eine genaue Abrechnung zu erhalten, können Sie den TLAB ausschalten. Es besteht immer noch das Risiko, dass ein GC das Ergebnis ändert, aber es ist viel weniger betroffen.

%Vor%

Hinweis: Bei umfangreichen Tests kann es hilfreich sein, wenn Sie die Eden-Größe auf 1 GB oder mehr erhöhen.

%Vor%
  

Für 64-Bit: 4 + 4 + 2 + 16 = 26 Bytes

Dies ist nicht möglich, da die HotSpot JVM immer nur Vielfache von 8 Bytes zuweist. BTW Sie können die minimale Zuordnungseinheit in Java 8 auf 16 oder 32 Bytes erhöhen.

Der Header ist 12 Bytes, also ist die Größe

4 + 4 + 2 + 12 (Header) + 2 (Auffüllen auf 8 Byte multiple) = 24

Führen Sie mit -XX:ObjectAlignmentInBytes=16 Sie sehen

aus %Vor%

Dies wirft die Frage auf, warum würden Sie das tun, wenn es Speicher verschwendet?

Der Vorteil bezieht sich auf komprimierte Oops, mit denen Sie 32-Bit-Referenzen in einer 64-Bit-JVM verwenden können. Normalerweise können komprimierte Oops 32 GB adressieren, weil sie weiß, dass alle Objekte um 8 Bytes ausgerichtet sind, d. H. Sie können 4 G * 8 Bytes adressieren. Wenn Sie jedoch die Byteausrichtung auf 16 erhöhen, können Sie 4G * 16 Byte oder 64 GB mit komprimierten Oops adressieren. Ich glaube, Sie können eine 32-Byte-Ausrichtung verwenden, aber dies verschwendet mehr Speicher als es in den meisten Fällen spart.

    
Peter Lawrey 19.07.2014 06:49
quelle
1

Das ist eine knifflige Frage.

Wenn Sie die Größe der Variablen berücksichtigen, ändert sich das nicht. Mit 32 und 64 Bits erhalten Sie 24 Bytes. Das 64-Bit wird jedoch mehr für den Speicher laden, und das wird mehr Speicher belegen.

Es hilft zu erinnern, dass 32 und 64 sind, was die CPU verarbeiten kann, nicht die Menge an Speicher, die zugewiesen werden kann.

Eine Referenz finden Sie in dieser Aussage von Red Hat:

9.1. 32-Bit- und 64-Bit-JVM

  

Eine häufig gestellte Frage bei der Leistungsdiskussion ist die bessere Gesamtleistung: 32-Bit- oder 64-Bit-JVM? Es scheint, dass eine 64-Bit-JVM, die von einem 64-Bit-Betriebssystem gehostet wird, besser als eine 32-Bit-JVM auf moderner, 64-Bit-fähiger Hardware funktioniert. Um einige quantitative Daten zu diesem Thema zu liefern, wurde das Testen mit einem Industriestandard-Workload durchgeführt. Alle Tests wurden auf demselben System ausgeführt, mit dem gleichen Betriebssystem, JVM-Version und Einstellungen mit einer Ausnahme, die unten beschrieben wird.

Quelle: Ссылка

    
Alexandre Santos 19.07.2014 05:02
quelle