En dehors de la heap, un autre monde existe…
A la base, la gestion automatique de la mémoire (nettoyage de la heap) par le Garbage Collector est une des raisons du succès de Java.
Cela a résolu la majorité des bugs que l’on rencontrait historiquement en C et C++, où l’on ne peut pas vérifier que le pointeur utilisé est associé à une zone de mémoire réservée.
De son côté, le principe de mémoire off-heap a été introduit par Java NIO, en Java 1.4 (avec les direct byte buffer, via la classe java.nio.ByteBuffer
et sa méthode allocateDirect
).
Après la création du compilateur JIT en Java 1.2, il s’agissait de continuer à améliorer les performances du langage, qui était sa grande critique du langage à ses débuts.
FAIRE UN SCHEMA base : http://fr.slideshare.net/rgrebski/on-heap-cache-vs-offheap-cache-53098109 détail : http://www.pointsoftware.ch/de/under-the-hood-runtime-data-areas-javas-memory-model/ retouche : https://anturis.com/blog/java-virtual-machine-the-essential-guide/
-
Heap
-
Eden Pool
-
Survivor Pool
-
Tenured Pool
-
-
Non Heap
-
Method Area
This area was previously known as the permanent generation where loaded classes were stored. It has recently been removed from the JVM, and classes are now loaded as metadata to native memory of the underlying OS. -
Native Area
This area holds references and variables of primitive types.
-
Voir l’article de Ippon pour le schéma de la mémoire et les explications associées (attention ! article de 2011) Voir également http://stackoverflow.com/questions/2129044/java-heap-terminology-young-old-and-permanent-generations (OU http://stackoverflow.com/a/31103100/1809195 pour avoir directement le commentaire)
Ajouter screenshot de l’onglet Memory de la JConsole ? >> avec la metaspace (ou class metadata space) qui remplace la permanent generation
Expliquer pour la Non Heap (Off Heap) le principe de Thread stack (voir Jenkov)
>> mémoire off-heap = native memory = direct memory (TODO A VERIFIER)
Permet d’outrepasser certaines contraintes inhérentes à Java :
-
pas fait pour gérer de très fortes volumétries de données
-
le GC n’est pas réellement fait pour gérer des objets
Pour rappel les fonctions du GC sont :-
de détecter les objets qui ne sont plus référencés (utilisés), et de les effacer
-
de déplacer les survivants d’une zone mémoire à l’autre, afin de libérer de la place pour l’affectation des nouveaux objets Cela convient bien à des objets dont le cycle de vie est court, et pour des JVM de l’ordre de quelques Go.
-
Par contre, ce principe ne semble pas adapté à des objets lourds et à cycle de vie (très) long.
Ces derniers vont en effet être régulièrement déplacés d’une zone mémoire à l’autre, ce qui va être d’autant plus pénalisant que les objets sont lourds, et que l’opération de déplacement du GC est bloquante (VERIFIER DANS QUELS CAS EXACTEMENT)
>> la mémoire off-heap n’est en elle-même ni plus ni moins rapide que la heap, The point of BigMemory is not that native memory is faster, but rather, it’s to reduce the overhead of the garbage collector having to go through the effort of tracking down references to memory and cleaning it up. TODO voir http://stackoverflow.com/a/5864736/1809195
Il s’agissait de trouver le meilleur ratio entre taille de la Heap et péjoration des performances dues aux traitements du Garbage Collector. Plus la taille de la Heap est grande, plus longues sont les opérations associées du Garbage Collector.
Une solution : sauvegarder les données dans une zone mémoire non gérée, non vue, par le GC (appelée mémoire Off-Heap)
-
Tous les objets instanciés à l’aide de
new
sont alloués dans la Heap. -
Non Direct ByteBuffer
-
Off Heap
-
utilisation de JNA
-
Direct ByteBuffer
-
MemoryMappedFile
-
sun.misc.Unsafe
-
Java nio a introduit le principe de mémoire off-heap (java 1.4). Java 7 l’a étendu (JSR-203/NIO2).
Les objets concernés sont les ByteBuffers (java.nio.ByteBuffer) avec allocation directe (maximum de 2 Go par buffer). Concrètement il s’agit d’un tableau de Bytes qui sera stocké dans la mémoire native. La nature même du stockage (sous forme binaire) impose une sérialisation/désérialisation des objets.
SYNTHESE DES 2 parties à faire
Cela passe par l’utilisation de la classe java.nio.ByteBuffer
et de sa méthode allocateDirect
.
Les objets instanciés à partir de cette classe vont être stockés dans la Heap.
Par contre, il va s’agir de "pointeurs", très légers en eux-mêmes, vers une zone mémoire en dehors de la Heap.
Manipuler le contenu de ces objets n’est pas sans contraintes :
-
ce contenu n’est accessible que par le principe de sérialisation / désérialisation.
En effet, ce que Java va stocker dans la mémoire off-heap va être considéré comme des octets (bytes), et non des objets.
Ceci n’est évidemment pas bon en termes de performance.
>> Direct memory can be faster than using a byte[] if you use use non bytes like int as it can read/write the whole four bytes without turning the data into bytes. However it is slower than using POJOs as it has to bounds check every access.
Par contre, c’est très performant pour les type primitifs. On ne met QUE des types primitifs en Off-Heap. Jenkov: All local variables of primitive types ( boolean, byte, short, char, int, long, float, double) are fully stored on the thread stack and are thus not visible to other threads.
De plus, la perte du pointeur vers le buffer rend ce dernier éligible à la garbage collection. La mémoire associée au pointeur est libérée au moment de la collecte.
>> il n’y a pas de méthode pour désallouer un objet stocké hors heap. En réalité une méthode de libération de la mémoire est crée automatiquement (sun.misc.Cleaner) et sera appelée par le GC lors de son prochain passage. sun.misc.Cleaner, se renseigner !
//Objet léger qui pointe vers la mémoire. ByteBuffer bb = ByteBuffer.allocateDirect(1024);
bb.putInt(15); bb.putChar('a'); bb.rewind();
int myInt = bb.getInt(); char myChar = bb.getChar();
Tout comme pour la heap, l’espace est libéré par le GC lorsque l’objet n’est plus référencé par le code. Tout comme la heap il n’y a pas de relation directe entre le moment ou l’objet est libérable et le moment ou il est effectivement libéré. Donc il n’y a pas de magie, les objets hors heap sont bien sensibles au GC.
Toutefois :
Pas de phase de marquage des objets. Pas de phase de compaction (réorganisation de l’espace mémoire) pendant le passage du GC. Le nettoyage de la mémoire hors heap est donc plus rapide que son homologue de la heap. Il est possible d’appeler la méthode de nettoyage à tout moment (encore une fois en fouillant dans les profondeurs de l’API) :
Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]); getCleanerMethod.setAccessible(true); sun.misc.Cleaner cleaner = (sun.misc.Cleaner)getCleanerMethod.invoke(buffer, new Object[0]); cleaner.clean();
DirectByteBuffer : il y a un overhead, du fait de certaines opérations supplémentaires, comme la détection de l’architecture petit-boutiste (little-endian), ou gros-boutiste de (big-endian) de l’OS sous-jacent. Pour la solution ActivePivot, la classe (DirectByteBuffer) a été réimplémentée afin de ne pas effectuer ces opérations supplémentaires. Cette réimplémentaion nécessite l’utilisation de la classe Unsafe
TODO : actualité, parler de la levée de boucliers devant la possible suppression de Unsafe en Java 9
Cette classe permet de manipuler directement la mémoire en Java. Elle est utilisée par ByteBuffer.allocateDirect().
A la base, elle n’est pas censé être utilisée en dehors du jdk. Son accès est protégé, et il faut donc se servir de l’introspection pour pouvoir l’utiliser. >> les constructeurs sont privés et la méthode de classe getUnsafe() ne peut être appelée que par un Bootloader (et donc par la JVM elle même). >> TODO : l’histoire du Bootloader est à préciser
Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null);
Avec Unsafe
, nous pouvons allouer de la mémoire à un emplacement dont on obtient l’adresse :
long address = unsafe.allocatememory(1024);
A partir de là, il est possible d’y insérer des données :
unsafe.putInt(address, 10);
en prenant soin de gérer manuellement leur position en mémoire
unsafe.putChar(address + 4, 'x')
Ici nous avions insérer dans un 1er temps un int, donc 4 octets, donc il faut tenir compte lors de l’ajout du char suivant.
autre example :
// Récupère une instance Unsafe Unsafe unsafe = getUnsafeInstance();
// Réserve de la mémoire directe Long allocateMemory = unsafe.allocateMemory(10);
// Récupération de l'espace d'allocation du champs code Commune Field field = Commune.class.getDeclaredField("codeCommune"); Long offsetCodeCommune = unsafe.objectFieldOffset(field);
// On affecte une valeur à l'emplacement du champ unsafe.putObject(allocateMemory, offsetCodeCommune, "325555");
-
ActivePivot : base de données en mémoire, écrite en Java, très grosse volumétrie, très fortes contraintes de performance
-
memory mapped file
-
OpenHFT (HigherFrequencyTrading) / Chronicle (nouveau nom / http://chronicle.software/) : Peter Lawrey is Lead Developper
-
Redis (REmote Dictionary Server, used by StackOverFlow, GitHub, Twitter)
-
histoire et genèse de la mémoire off-heap
-
http://www.soat.fr/wp-content/uploads/2015/10/Java-8-Migration-et-enjeux-strat%C3%A9giques-en-entreprise.pdf : Livre blanc José Paumard et So@t, "Java 8 - Migration et enjeux stratégiques en entreprise", section "Un peu d’histoire"
-
https://www.jcp.org/en/jsr/detail?id=51 : JSR-51 sur
java.nio
, voir plus particulièrement la section "2.3 What needs of the Java community will be addressed by the proposed specification?"
-
-
http://www.touilleur-express.fr/2015/01/14/parisjug-soiree-youngblood/
Sujet 3 : Faire tourner une JVM avec 4 To de mémoire : yes we can par Gaëlle Guimezanes
Travaille chez QuartetFS, éditeur d’ActivePivot, solution d’analyse multidimensionnelle
Voir la vidéo https://www.youtube.com/watch?v=Cskt4qtNeEI -
http://blog.ippon.fr/2011/11/03/java-acces-directs-a-la-memoire-off-heap/ : présentation complète de la mémoire off-heap
-
http://blog.ippon.fr/2011/08/29/lmax-6-millions-doperations-par-seconde/ : A la base, article sur le LMAX Disrupter, mais à voir également pour le tableau des coûts de lecture en fonction du type de mémoire
-
java.nio.ByteBuffer : surtout la section Direct vs. non-direct buffers
-
http://shekup.blogspot.fr/2011/11/java-runtime-memory-management.html : rechercher ByteBuffer pour un schéma sur la NativeHeap (Off-Heap memory)
-
http://www.javacodegeeks.com/2013/08/which-memory-is-faster-heap-or-bytebuffer-or-direct.html : voir les exemples de code dans les commentaires
-
voir http://chronicle.software/products/chronicle-queue/ pour un exemple et des explications poussées sur le memory mapped file
-
voir sur PluralSight : https://app.pluralsight.com/library/courses/understanding-java-vm-memory-management/table-of-contents
-
http://stackoverflow.com/questions/22332990/java-heap-vs-direct-memory-access# : pour qui est le plus rapide entre mémoire off-heap et heap
-
http://stackoverflow.com/questions/5863316/is-java-native-memory-faster-than-the-heap : bonne explication de pourquoi il ne faut pas utiliser la mémoire off-heap avec les POJO
-
https://dzone.com/articles/heap-vs-heap-memory-usage : pour les explications autour de OpenHFT (Chronicle Queue & Co), et les RISQUES et INCONVENIENTS de la mémoire off-heap
-
http://www.infoq.com/articles/Open-JDK-and-HashMap-Off-Heap : article complet (par Peter Lawrey) qui insiste sur la HashMap off-heap.
-
OpenHFT / Chronicle
-
https://www.youtube.com/watch?v=NEG8tMn36VQ : une (ancienne) présentation de OpenHFT par Peter Lawrey (Lead developper de OpenHFT)
-
-
Modèle mémoire :
-
http://tutorials.jenkov.com/java-concurrency/java-memory-model.html : explication complète du modèle, mais sans parler explicitement de mémoire off-heap.
Voir tout particulièrement la section The Internal Java Memory Model -
https://anturis.com/blog/java-virtual-machine-the-essential-guide/ : bon schéma et explications sur le modèle mémoire Java (article récent, JDK 8) A UTILISER EN 3e !!!! (prend en compte la suppression de la permgen)
-
http://sdjournal.org/java-8-removal-of-permgen-beta/ : remplacement de la Permanent Generation par la MetaSpace et explications associées.
-
http://java-latte.blogspot.in/2014/03/metaspace-in-java-8.html : bonne explication sur le nouveau metaspace, SURTOUT CORRECTE (il montre bien que la perm gen fait partie de la heap)
-
http://www.oracle.com/technetwork/java/javase/memorymanagement-whitepaper-150215.pdf : ancien memory model en Java 5, explique bien que la permanent generation fait partie de la heap.
-
http://fr.slideshare.net/rgrebski/on-heap-cache-vs-offheap-cache-53098109 : pour un schéma récent du Memory Model avec la Metaspace, ainsi que des comparaisons Off-Heap / Heap avec perfs associées, TRES BON et récent, A UTILISER COMME BASE !!!!
jeter également un oeil à son repo https://github.com/rgrebski/confitura2015
Et voici sa vidéo au confiturapl 2015 : https://www.youtube.com/watch?v=wfPl_aNj4Pc (Attention ! Pas en anglais !) : propose une comparaison des caches Chronicle / Hazelcast / Redis -
http://fr.slideshare.net/IonutBalosin/evolution-of-garbage-collection-in-hotspot-java-virtual-machine : bon schéma du memory model
-
http://daniel.mitterdorfer.name/talks/2015/jvm-deep-dive-ljug/#/41 : schéma du slide 41 sympa, et bon schéma des différents GC existants
-
http://www.pointsoftware.ch/de/under-the-hood-runtime-data-areas-javas-memory-model/ : bon schéma du memory model, détaillé A UTILISER POUR DETAIL !!!!
-
http://stackoverflow.com/questions/2129044/java-heap-terminology-young-old-and-permanent-generations : bon explication des différentes zones du modèle mémoire
-