
L'article se concentrera sur l'implémentation de la compression du pointeur dans la machine virtuelle Java 64 bits , qui est contrôlée par l'option UseCompressedOops et est activée par défaut pour les systèmes 64 bits commençant par Java SE 6u23.
Description du problème
Dans une machine virtuelle Java 64 bits, les pointeurs prennent 2 fois plus d'espace mémoire (surprise-surprise) que dans une mémoire 32 bits. Cela peut augmenter la taille des données de 1,5 fois par rapport au même code pour l'architecture 32 bits. Dans le même temps, dans une architecture 32 bits, seuls 2 ^ 32 octets (4 Go) peuvent être adressés, ce qui est assez petit dans le monde moderne.
Écrivons un petit programme et regardons combien d'octets les objets entiers occupent:
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); } }
Ici, nous mettons en évidence un million d'objets de la classe Integer et nous endormons pendant longtemps. La dernière ligne est nécessaire pour que le compilateur n'ignore pas soudainement la création du tableau (bien que sur ma machine, les objets soient créés normalement sans cette ligne).
Nous compilons et exécutons le programme avec la compression de pointeur désactivée:
> javac HeapTest.java > java -XX:-UseCompressedOops HeapTest
En utilisant l'utilitaire jcmd , nous regardons l'allocation de mémoire:
> jps 45236 HeapTest ... > jcmd 45236 GC.class_histogram

L'image montre que le nombre total d'objets est de 1000128 et que la taille de la mémoire occupée par ces objets est de 24003072 octets . C'est-à-dire 24 octets par objet (pourquoi exactement 24 seront écrits ci-dessous).
Et voici la mémoire du même programme, mais avec le drapeau UseCompressedOops activé :

Désormais, chaque objet occupe 16 octets .
Les avantages de la compression sont évidents =)
Solution
Comment la JVM compresse-t-elle les pointeurs? Cette technique est appelée Oups compressés . Oop signifie pointeur d'objet ordinaire ou pointeur d'objet ordinaire .
L'astuce est que dans un système 64 bits, les données en mémoire sont alignées avec le mot machine, c'est-à-dire 8 octets chacun. Et l'adresse a toujours trois bits zéro à la fin.
Si vous enregistrez le pointeur en décalant l'adresse de 3 bits vers la droite (l'opération est appelée encoder ), et avant d'utiliser, décaler de 3 bits vers la gauche (respectivement, décoder ), vous pouvez adapter des pointeurs 32 bits d'une taille de 35 bits , c'est-à-dire Adresse jusqu'à 32 Go (2 ^ 35 octets).
Si la taille du segment de mémoire de votre programme dépasse 32 Go, la compression cesse de fonctionner et tous les pointeurs deviennent 8 octets.
Lorsque l'option UseCompressedOops est activée, les types de pointeurs suivants sont compressés:
- Champ de classe pour chaque objet
- Objets de champ de classe
- Éléments d'un tableau d'objets.
Les objets de la JVM elle-même ne sont jamais compressés. Dans ce cas, la compression se produit au niveau de la machine virtuelle, et non au bytecode.
En savoir plus sur la mise en mémoire d'objets
Maintenant, utilisons l'utilitaire jol (Java Object Layout) pour voir de plus près combien de mémoire notre Integer prend dans différentes JVM:
> 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
La différence entre «VM 64 bits» et «VM 64 bits, références compressées activées» est de réduire l'en- tête d'objet de 4 octets. De plus, dans le cas sans compression, il devient nécessaire d'ajouter 4 octets supplémentaires pour aligner les données en mémoire.
Qu'est-ce que cet en-tête d'objet? Pourquoi at-il diminué de 4 octets?

L'image montre un en-tête d'objet de 12 octets, c'est-à-dire avec l'option UseCompressedOops activée. L'en-tête se compose de quelques indicateurs JVM internes, ainsi que d'un pointeur vers la classe de cet objet. On peut voir que le pointeur vers la classe prend 32 bits. Sans compression, il occuperait 64 bits et la taille de l'en-tête d'objet serait déjà de 16 octets.
Au fait, vous pouvez voir qu'il existe une autre option pour l'alignement sur 16 octets. Dans ce cas, vous pouvez augmenter la mémoire jusqu'à 64 Go.
Contre Compression des pointeurs
La compression des pointeurs, bien sûr, a un inconvénient évident - le coût des opérations de codage et de décodage chaque fois que le pointeur est accédé. Les nombres exacts varieront selon l'application.
Par exemple, voici un graphique des pauses du ramasse-miettes pour les pointeurs compressés et non compressés, tirées d'ici Java GC en chiffres - POO compressés

On peut voir qu'avec la compression activée, les pauses GC durent plus longtemps. Vous pouvez en savoir plus à ce sujet dans l'article lui-même (l'article est assez ancien - 2013).
Références:
Oups compressés dans la JVM Hotspot
Comment JVM alloue-t-il les objets
CompressedOops: Introduction aux références compressées en Java
Astuce derrière les Oops compressés de JVM
Améliorations des performances des machines virtuelles Java HotSpot