Einführung
Enthält das Java-Objekt:
- Felder in der Oberklasse deklariert?
- private Felder in einer Oberklasse deklariert?
- Methoden?
- Array-Elemente?
- Array Länge?
- ein anderes Objekt (an sich)?
- Hash-Code?
- Typ (eigene)?
- Name (eigener)?
Antworten auf diese (und andere) Fragen erhalten Sie über
die Klassenbibliothek org.openjdk.jol , mit der wir insbesondere verstehen können, dass das Objekt ein Speicherbereich ist:
- enthält:
- Header (bis zu 16 Bytes) und darin:
- Hash-Code
- Typreferenz
- Array-Länge (für Array)
- Alle Felder (einschließlich privat), die in allen Oberklassen deklariert sind
- oder Array-Elemente (für ein Array)
- nicht enthalten:
- statische Variablen
- Methoden
- andere Objekte in dir
- eigener Name (dh das Objekt hat keinen Namen)
Vorbereitung
Hier sind die Ergebnisse der Auswertung des Speichers von Objekten verschiedener Typen mit der Methode aus der Beschreibung des
Pakets java.lang.instrument (siehe auch
hier ). Diese Ergebnisse ermöglichen es uns, die meisten der oben gestellten Fragen zu beantworten.
Die folgenden Schritte müssen ausgeführt werden:
- Erstellen Sie eine Agentenklasse mit der Premain-Methode:
public static void premain(String, Instrumentation) {...}
- Erstellen Sie ein Archiv mit der Agentenklasse und der Manifestdatei mit dem Inhalt:
Premain-class: --
- Erstellen Sie eine ausführbare Klasse zur Auswertung des Speichers.
- Geben Sie beim Starten der virtuellen Maschine das Archiv mit dem Parameter "-javaagent" an:
java -javaagent:- --
Beginnen wir mit einem Testfall. Der Einfachheit halber verwenden wir ein unbenanntes Paket.
Schritt 1. Erstellen Sie eine Testagentenklasse
import java.lang.instrument.Instrumentation; public class A { public static void premain(String notUsedHere, Instrumentation i) { System.out.println("premain"); } }
Wir kompilieren:
javac A.java
Schritt 2. Erstellen Sie eine m.txt-Manifestdatei mit:
Premain-class: A
ACHTUNG: Die zweite Zeile der Datei muss leer sein und darf keine Leerzeichen enthalten.Erstellen Sie das A.jar-Archiv:
jar cmf m.txt A.jar A.class
Schritt 3. Erstellen Sie eine ausführbare Testklasse
public class M { public static void main(String[] notUsedHere) {
Wir kompilieren:
javac M.java
Schritt 4. Durchführen
java -javaagent:A.jar M
Ergebnis:
premain main
Gibt an, dass zuerst die Premain-Methode der Agentenklasse und dann die Hauptmethode der ausführbaren Klasse aufgerufen wurde.
Erstellen Sie nun die gewünschte Agentenklasse:
import java.lang.instrument.Instrumentation; public class A {
und ausführbare Klasse:
class M { public static void main(String[] notUsedHere) { mem("Object", new Object()); } private static void mem(Object o, Object ref) { System.out.println(o + ": " + objectBytesEstimate(ref)); } private static long objectBytesEstimate(Object ref) { if (A.instrumentation() == null) { throw new RuntimeException("Not initialized instrumentation."); } return A.instrumentation().getObjectSize(ref); } }
Methode
long getObjectSize(Object --)
Gibt eine SCHÄTZUNG der Größe (Anzahl der Bytes) des Speichers zurück, der vom Objekt an der angegebenen Verbindung belegt wird. Es muss berücksichtigt werden, dass die resultierende Schätzung für eine andere virtuelle Maschine unterschiedlich sein kann. Die Werte für
jdk-13 werden hier aufgelistet.
Wir führen aus:
javac *.java jar cmf m.txt A.jar A.class java -javaagent:A.jar M
und wir bekommen das Ergebnis:
Object: 16
Dies zeigt, dass ein leeres Objekt vom Typ Object hier (BY ASSESSMENT) 16 Bytes belegt. Von diesen belegen 12 Bytes den Header, und 4 Bytes am Ende dienen dazu, die Länge des Objekts an einer 8-Byte-Grenze auszurichten.
Ergebnisse
Weitere Beispiele enthalten nur den Code, der in der Hauptmethode der Klasse M platziert ist. Sie sollten für jedes Beispiel mit den folgenden Befehlen ausgeführt werden:
javac M.java java -javaagent:A.jar M
Das erneute Erstellen von A.jar ist nicht erforderlich.
Um beispielsweise eine Schätzung der Speichergröße eines Objekts eines beliebigen Typs ohne Felder zu erhalten, fügen wir den Code in die Hauptmethode ein:
class C {}; mem("Empty", new C());
Das im Kommentar angegebene Ergebnis zeigt, dass ein Objekt ohne Felder so viele Bytes belegt wie ein Objekt vom Typ Objekt.
Weiter das Ergebnis des Programms:
{class C {int a; } mem(1, new C());}
gibt an, dass jedes int-Feld 4 Bytes benötigt. Ich stelle fest, dass hier jede Zeile ein separater Block ist, mit dem Sie denselben Namen für verschiedene Klassen verwenden können.
Jedes lange Feld besteht aus 8 Bytes:
{class C {long a; } mem(1, new C());}
Jedes boolesche Feld benötigt 1 Byte (für diese VM):
{class C {boolean a; } mem(1, new C());}
Jedes Referenzfeld benötigt 4 Bytes (für diese VM):
{class C {Boolean a; } mem(1, new C());}
Ein Feld vom Typ String benötigt wie jede Referenz auch 4 Byte:
{class C {String a; } mem(" null", new C());}
Ein Array-Referenzfeld benötigt wie jedes Referenzfeld auch 4 Bytes:
{class C {int[] a; } mem("null", new C());}
Das Subtypobjekt enthält jedes in der Oberklasse deklarierte Feld, unabhängig vom Zugriffsmodifikator:
{class S { } class C extends S {long a;} mem("0+1", new C());}
Das Subtypobjekt enthält ein Feld, das in der Oberklasse mit demselben Namen wie in der Unterklasse deklariert ist (das sogenannte versteckt - versteckt):
{class S { } class C extends S {long a,b;} mem("0+2", new C());}
Ein Subtypobjekt enthält jedes Feld, das in jeder seiner Oberklassen deklariert ist:
class U {private long a; } class S extends U {private long a; } class C extends S { long a; } mem("1+1+1", new C());
Wenden Sie sich an Arrays. Wie Sie wissen, ist ein Array eine spezielle Art von Objekt, dessen Elemente sich im Objekt selbst befinden. Daher wächst die Größe des vom Array belegten Speichers mit der Anzahl der Elemente:
{long[] a = new long[ 0]; mem(" 0", a);}
Und für die Reihe von Links:
{Long[] a = new Long[ 0]; mem(" 0", a);}
Aus Neugier vergleichen wir nun die Größen mehrerer Objekte unterschiedlichen Typs:
mem(" Object", new Object());
Das gleiche gilt für verschiedene JDK auf einem 64-Bit-Prozessor:
jdk1.6.0_45 jdk1.7.0_80 jdk1.8.0_191 jdk-9 jdk-12 jdk-13
----------- ----------- ------------ ------ ------ ---- - -
Objekt: 16 16 16 16 16 16
Zeichenfolge: 32 24 24 24 24 24
Ausnahme: 32 32 32 40 40 40
int.class: 104 88 104 112 104 112
int []. class: 584 544 480 112 104 112
Objektklasse: 600 560 496 112 104 112
Systemklasse: 624 560 496 144 152 160
String.class: 696 640 624 136 128 136
Die Schätzung der Stringgröße beträgt 24 Byte, obwohl die String-Klasse viele statische Variablen sowie statische und nicht statische Methoden enthält. Dies weist zweifellos auf das Fehlen statischer Variablen und Methodencodes im Objekt hin. Gleiches gilt für ein Objekt jeglichen Typs.
Abschließend eine Erinnerung: Alle Daten zur Größe des Objekts werden geschätzt und können von Ausführung zu Ausführung und natürlich für verschiedene virtuelle Maschinen in gewissem Maße variieren.