
In diesem Artikel wird die Implementierung der Zeigerkomprimierung in der 64-Bit-Java Virtual Machine erläutert , die von der Option UseCompressedOops gesteuert wird und standardmäßig für 64-Bit-Systeme ab Java SE 6u23 aktiviert ist.
Problembeschreibung
In einer 64-Bit-JVM belegen Zeiger zweimal mehr (Überraschungs-Überraschungs-) Speicherplatz als in einer 32-Bit-JVM. Dies kann die Datengröße im Vergleich zum gleichen Code für die 32-Bit-Architektur um das 1,5-fache erhöhen. Gleichzeitig können in der 32-Bit-Architektur nur 2 ^ 32 Bytes (4 GB) adressiert werden, was in der modernen Welt recht klein ist.
Schreiben wir ein kleines Programm und schauen wir uns an, wie viele Bytes Integer-Objekte belegen:
import java.util.stream.IntStream; import java.util.stream.Stream; class HeapTest { public static void main(String ... args) throws Exception { Integer[] x = IntStream.range(0, 1_000_000).boxed().toArray(Integer[]::new); Thread.sleep(6000000); Stream.of(x).forEach(System.out::println); } }
Hier markieren wir eine Million Objekte der Integer-Klasse und schlafen lange ein. Die letzte Zeile wird benötigt, damit der Compiler die Erstellung des Arrays nicht plötzlich ignoriert (obwohl auf meinem Computer Objekte normalerweise ohne diese Zeile erstellt werden).
Wir kompilieren und führen das Programm mit deaktivierter Zeigerkomprimierung aus:
> javac HeapTest.java > java -XX:-UseCompressedOops HeapTest
Mit dem Dienstprogramm jcmd sehen wir uns die Speicherzuordnung an:
> jps 45236 HeapTest ... > jcmd 45236 GC.class_histogram

Das Bild zeigt, dass die Gesamtzahl der Objekte 1000128 beträgt und der Speicher, den diese Objekte belegen, 24003072 Byte beträgt . Das heißt, 24 Bytes pro Objekt (warum genau 24 werden unten geschrieben).
Und hier ist der Speicher desselben Programms, jedoch mit aktiviertem UseCompressedOops- Flag:

Jetzt belegt jedes Objekt 16 Bytes .
Die Vorteile der Komprimierung liegen auf der Hand =)
Lösung
Wie komprimiert die JVM Zeiger? Diese Technik wird als Compressed Oops bezeichnet . Oop steht für gewöhnlichen Objektzeiger oder gewöhnlichen Objektzeiger .
Der Trick besteht darin, dass in einem 64-Bit-System die Daten im Speicher mit dem Maschinenwort ausgerichtet sind, d. H. Jeweils 8 Bytes. Und die Adresse hat am Ende immer drei Nullbits.
Wenn Sie den Zeiger speichern, indem Sie die Adresse um 3 Bit nach rechts verschieben (die Operation wird als Codierung bezeichnet ) und vor der Verwendung um 3 Bit nach links verschieben (bzw. decodieren ), können Sie 32-Bit-Zeiger mit einer Größe von 35 Bit anpassen , d. H. Adresse bis zu 32 GB (2 ^ 35 Byte).
Wenn die Heap-Größe Ihres Programms mehr als 32 GB beträgt, funktioniert die Komprimierung nicht mehr und alle Zeiger werden 8 Byte groß.
Wenn die Option UseCompressedOops aktiviert ist, werden die folgenden Zeigertypen komprimiert:
- Klassenfeld für jedes Objekt
- Klassenfeldobjekte
- Elemente eines Arrays von Objekten.
Die Objekte der JVM selbst werden niemals komprimiert. In diesem Fall erfolgt die Komprimierung auf der Ebene der virtuellen Maschine und nicht auf Bytecode.
Lesen Sie mehr über das Platzieren von Objekten im Speicher
Verwenden wir nun das Dienstprogramm jol (Java Object Layout), um genauer zu untersuchen, wie viel Speicher unsere Ganzzahl in verschiedenen JVMs belegt:
> java -jar jol-cli-0.9-full.jar estimates java.lang.Integer ***** 32-bit VM: ********************************************************** java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 8 (object header) N/A 8 4 int Integer.value N/A 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ***** 64-bit VM: ********************************************************** java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 16 (object header) N/A 16 4 int Integer.value N/A 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ***** 64-bit VM, compressed references enabled: *************************** 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 Space losses: 0 bytes internal + 0 bytes external = 0 bytes total ***** 64-bit VM, compressed references enabled, 16-byte align: ************ 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 Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Der Unterschied zwischen "64-Bit-VM" und "64-Bit-VM, komprimierte Referenzen aktiviert" besteht darin, den Objektheader um 4 Byte zu reduzieren. Außerdem müssen im Fall ohne Komprimierung 4 weitere Bytes hinzugefügt werden, um die Daten im Speicher auszurichten .
Was ist dieser Objektheader? Warum wurde es um 4 Bytes verringert?

Das Bild zeigt einen Objektheader von 12 Bytes, d.h. mit aktivierter Option UseCompressedOops. Der Header besteht aus einigen internen JVM-Flags sowie einem Zeiger auf die Klasse dieses Objekts. Es ist ersichtlich, dass der Zeiger auf die Klasse 32 Bit benötigt. Ohne Komprimierung würde es 64 Bit belegen und die Größe des Objekt-Headers würde bereits 16 Bytes betragen.
Übrigens können Sie sehen, dass es eine weitere Option für die 16-Byte-Ausrichtung gibt. In diesem Fall können Sie den Speicher auf bis zu 64 GB erhöhen.
Nachteile Komprimierung von Zeigern
Das Komprimieren von Zeigern hat natürlich ein offensichtliches Minus - die Kosten für Codierungs- und Decodierungsoperationen bei jedem Zugriff auf den Zeiger. Die genauen Zahlen variieren je nach Anwendung.
Hier ist beispielsweise ein Diagramm der Pausen des Garbage Collectors für komprimierte und nicht komprimierte Zeiger, die hier von Java GC in Numbers - Compressed OOPs übernommen wurden

Es ist ersichtlich, dass bei aktivierter Komprimierung die GC-Pausen länger dauern. Sie können mehr darüber im Artikel selbst lesen (der Artikel ist ziemlich alt - 2013).
Referenzen:
Komprimierte Hoppla in der Hotspot-JVM
Wie ordnet JVM Objekte zu?
CompressedOops: Einführung in komprimierte Referenzen in Java
Trick hinter JVMs komprimiertem Ups
Leistungsverbesserungen für Java HotSpot Virtual Machine