Java-Zeigerkomprimierung


    In diesem Artikel wird die Implementierung der Zeigerkomprimierung in der Java Virtual Machine 64-Bit 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 Speicherplatz (Überraschung-Überraschung) 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 Byte (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 Klasse Integer 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 starten das Programm mit deaktivierter Zeigerkomprimierung:


    > javac HeapTest.java
    > java -XX:-UseCompressedOops HeapTest

    Mit dem Dienstprogramm jcmd untersuchen wir die Speicherzuordnung:


    > jps
    45236 HeapTest
    ...
    > jcmd 45236 GC.class_histogram



    Das Bild zeigt, dass die Gesamtzahl der Objekte 1000128 und die Größe des Speichers, den diese Objekte belegen, 24003072 Byte beträgt . Das heißt 24 Bytes pro Objekt (warum genau 24 unten geschrieben werden).


    Und hier ist der Speicher des gleichen 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 komprimiertes Hoppla bezeichnet . OOP steht für gewöhnliche Objektzeiger oder einen gewöhnlichen Zeiger auf ein Objekt .


    Der Trick besteht darin, dass in einem 64-Bit-System die Daten im Speicher auf das Maschinenwort ausgerichtet sind, d.h. Jeweils 8 Bytes. Und die Adresse hat am Ende immer drei Null-Bits.


    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 Bytes).


    Wenn die Größe des Heapspeichers für Ihr Programm 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-Ebene.


    Weitere Informationen zum Platzieren von Objekten im Speicher


    Sehen wir uns nun mit dem Dienstprogramm jol (Java Object Layout) genauer an, 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 ohne Komprimierung 4 weitere Bytes hinzugefügt werden, um die Daten im Speicher auszurichten .


    Was ist dieser Objektkopf? Warum hat es sich um 4 Bytes verringert?



    Das Bild zeigt einen Objektkopf 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 zu sehen, dass der Zeiger auf die Klasse 32 Bits benötigt. Ohne Komprimierung würde es 64 Bit belegen und die Größe des Objekt-Headers würde bereits 16 Byte betragen.


    Übrigens können Sie sehen, dass es eine andere Option für die 16-Byte-Ausrichtung gibt. In diesem Fall können Sie den Speicher auf bis zu 64 GB erhöhen.


    Cons Komprimierung von Zeigern


    Das Komprimieren von Zeigern hat natürlich ein offensichtliches Minus - die Kosten für das Codieren und Decodieren jedes Mal, wenn auf den Zeiger zugegriffen wird. Die genauen Zahlen variieren je nach Anwendung.


    Beispiel: Hier ist ein Diagramm mit den Pausen des Pausen-Garbage-Collectors für komprimierte und nicht komprimierte Zeiger, die von Java GC in Numbers - Compressed OOPs stammen



    Es ist zu sehen, dass bei aktivierter Komprimierung die GC-Pause länger dauert. Sie können mehr darüber im Artikel selbst lesen (der Artikel ist ziemlich alt - 2013).


    Referenzen:


    Die oops im Druck der Hotspot der JVM
    Wie funktioniert die JVM des zuteilen Objekte
    CompressedOops: Einführung in dem Druck Referenzen in dem Java -
    Trick hinter dem Druck JVM Oops
    die Java HotSpot Virtual Machine der Leistungsverbesserungen


    Jetzt auch beliebt: