Java : Accès directs à la mémoire (off-Heap)

La zone mémoire la plus connue d’une JVM est la heap mais ce n’est pas la seule zone de stockage disponible.

Nous verrons comment est structurée la mémoire d’une JVM et comment allouer de l’espace en dehors de la Heap ?

Et ce, dans le but de pouvoir conserver les données en mémoire même pour les applications les plus consommatrices.

Points abordés :

  • Quelles sont les méthodes pour instancier de la mémoire hors heap
  • Les cas d’applications (Cache, Memory Mapped File)
  • Performances
  • Problèmes rencontrés

Introduction et théorie

Voici les différentes zones que l’on peut retrouver dans un process Java :

Process Java

On y trouve des zones techniques : JVM, OS.

Des zones de stockage de données : Mémoire Heap et native.

La zone d’échange entre la JVM et l’OS : Librairies.

La gestion de la memoire Java est parfois assez déroutante pour les débutants : les paramètres de gestion les plus connus sont ms et mx qui régissent la taille de la Heap alors que nous venons de voir que ce n’est pas la seule zone mémoire d’une JVM.

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.

Tout d’abord le tableau suivant montre pourquoi il faut privilégier la mémoire aux autres types d’accès.

Comparaison des coûts de lecture en fonction du type de mémoire

Technologie Vitesse lecture
Mémoire (RAM)  10-60 ns
Accès réseau 10000-30000 ns
Disques SSD 70000-120000 ns
Disques durs 3000000-10000000 ns

La conclusion semble évidente : pour des raisons de performances, il faut privilégier la mémoire locale à tout autre type de stockage.

Gabarge Collector et problématiques

Longtemps la taille maximale allouée à une JVM a été limitée par différents facteurs :

  • Architecture 32 bits du processeur.
  • Système d’exploitation hébergeant la JVM (taille maximale d’un processus, maximum de RAM supportée)

Mais aussi, et c’était souvent le premier seuil atteint, par le comportement du Garbage Collector.

Il s’agissait de trouver le meilleur ratio entre taille de la Heap et péjoration des performances dues aux traitements du Garbage Collector.
De même, bien que plus rare que dans les langages sans gestion automatique de la mémoire, un programme Java n’est pas à l’abri des fuites mémoires.

C’est pourquoi, malgré la multiplication des architectures et OS 64 bits, cette barière n’a que peut évoluée depuis les débuts de Java.
On peut la fixer à 3-4 Go.

Conscients de cette limite, les éditeurs tentent de réagir.

Oracle à introduit le collecteur G1 (Garbage first) en version 6 de Java SE.
Ce collecteur a pour but de réduire les temps de passage du GC.

Azul System, s’est aussi positionné, avec la Zing Virtual Machine (Pauseless Garbage Collection).

En attendant, il est tentant d’utiliser le stockage hors heap dans des applications Java.

Mémoire off-heap

Une belle définition sous forme de lapalissade pourrait être : « tout ce qui n’est pas dans la heap est off heap ».

Tout d’abord voyons comment est structurée la mémoire dans la JVM

A – Zones méthodes (Method Area)

Cette zone est essentiellement alimentée par le classloader.
Quand la JVM charge une classe ou une interface, elle place les informations nécessaires dans cette zone.
Cette zone est éligible au Garbage Collector. Quand une classe passe hors scope elle sera collectée lors du prochain passage du GC.
Cette zone est partagée (accessible à tous les process de la JVM).

1 – Pool constantes :

Contient toutes les constantes définies par l’application :

Littérales : int, long, float, double ou bien String.
Références vers les types, les champs et les méthodes.

2 – Code méthode :

Implémentation (bytecode) de toutes les méthodes utilisées par la JVM.

3 – Zone d’informations :

Contient différents éléments d’information sur les classes.
Champs de classe :

  • Nom du champ.
  • Type du champ.
  • Modificateurs du champ.

Méthodes :

  • Nom de la méthode.
  • Type retourné.
  • Nombre et type des paramètres.
  • Modificateurs de la méthode.

En plus pour les méthodes non abstraites ou natives :

  • Taille de l’opérande et des variables locales (utilisée pour frame stack).
  • Table d’exception.

4 – Variables de classes

Les variables de classes sont partagées par toutes les instances de la classe. De plus elles ne sont pas liées à l’instanciation d’une classe mais créées au chargement de la classe.

B – Heap

Lorsque la JVM doit instancier un objet java, elle le fait dans cette zone.
On y retrouve les classes, les objets, les primitives Java.
Cette zone est nettoyée par le GC.

Cette zone est partagée (accessible à tous les process de la JVM).

NB : il y a une seule Heap et Method Area par JVM.

C – Threads

Cette zone n’est pas partagée.

Pointeur d’instruction :

Indique la ligne de code actuellement exécutée.

Java stack :

Stocke les primitives et les références.

Stocke l’état des méthodes invoquées (non natives) par le thread, l’état comprend les variables locales à la méthode (ThreadLocal), les paramètres d’appel ainsi que la valeur retournée par la méthode.

Chaque état est stocké dans des frames (un par méthode) dont la durée de vie est liée au cycle d’appel de la méthode.

Cette zone est nettoyée par le GC.

Cette zone est parcourue par le GC afin d’identifier les objets non référencés dans la Heap.

Frames :

Trois sections compose les frames.

Piles opérande

  • Pile FIFO 32/64 bits utilisée pour le stockage provisoire des variables ainsi que des calculs intermédiaires.

Données

  • Références RCP (Runtime Constant Pool).
  • Références vers le pool de constantes utilisées par le thread.
  • Résultats des retours de méthodes.

Variables locales

  • Tableau contenant les variables locales de la méthode. Soit les variables dont la durée de vie est celle de la méthode ainsi que les paramètres d’appel.

Stack Frames

Méthodes natives

A l’image des Frames l’état des méthodes natives est stocké dans cette zone.
Mais contrairement à ces dernières, l’organisation est dépendante de l’OS et de l’implémentation de la méthode native.

Création d’objets en dehors de la heap Java

L’utilisation dans un programme Java de mémoire hors heap devra répondre aux problématiques suivantes :

  • Accéder à la mémoire hors heap.
  • Gestion de la mémoire.
  • Référencement et accès à la mémoire off-heap.

A – Java Native Access (JNA)

JNA permet l’accès aux librairies natives (dll, so) à partir de code Java uniquement (sans utiliser JNI ou du C).
Une interface java décrit les méthodes et les structures de la librairie native.
Il n’y a pas de génération de code tout se fait au Runtime.

JNA est fourni avec certaines librairies natives courantes ainsi que des classes utilitaires d’accès à la mémoire native.

Plus d’informations :

Documentation : http://www.root-me.org/fr/Documentation/Hacking/Java-native-code-injection.html
Site web : https://github.com/twall/jna

Cette librairie fournie plusieurs méthodes pour créer des objets en dehors de la heap.

int value = 12345;
int offset = 0;
int size = 128;

// on crée un objet mémory
Memory m = new Memory(1024);

// on alloue l'espace mémoire
ByteBuffer buff = m.getByteBuffer(0, size);

offset = offset + size;

// On insère l'objet
buff.putInt(value);

// RAZ de la position courante du buffer
buff.flip();

// On récupère et affiche l'objet
System.out.println(buff.getInt());

// On déclare un deuxième buffer
ByteBuffer buff2 = m.getByteBuffer(offset, size);

// On insère l'objet
buff2.putInt(value + 1);

// RAZ de la position courante du buffer
buff2.flip();

// On récupère et affiche l'objet
System.out.println(buff2.getInt());

// on libère la mémoire
m.clear();

// méthode 2
// on crée un objet mémory
// et on insère directement les objets
Memory m2 = new Memory(1024);

// On insère l'objet
m2.setInt(offset, value);

// On récupère et affiche l'objet
System.out.println(m2.getInt(offset));

// on libère la mémoire
m2.clear();

B – Utilisation de la classe ByteBuffer

L’utilisation de la mémoire native avec cette API est simple, voire transparente.
Deux méthodes de création d’un ByteBuffer sont disponibles :

  1. ByteBuffer.allocateDirect()
  2. ByteBuffer.allocate()

Seule la première méthode instancie un objet en dehors de la heap.

Exemple de code :

byte[] bytes = ...;
// allocate native memory to store our object
ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
buf.put(bytes);

Remarque : Le contenu stocké doit être de type tableau de byte, ce qui explique que le contenu stocké hors heap doit être sérialisable.

Nota Bene : Ceux qui on déjà codé en C/C++ ne devront pas s’attendre a retrouver l’équivalent de malloc.
En effet les possibilités offertes sont très limitées et le plus surprenant étant qu’il n’y a pas de méthode pour désallouer un objet stocké hors heap.

Comment ça marche alors ?
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.

Conclusion :

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();

C – Autres possibilité d’allocation directe

sun.misc.Unsafe

Son utilisation est un peu plus complexe, il s’agit de la classe qui est derrière ByteBuffer.allocateDirect().
Comme son nom l’indique cette classe est unsafe et doit donc être utilisée en toute connaissance de cause. En effet l’accès à une zone mémoire non allouée provoque immanquablement le crash de la JVM.
Pour plus de sécurité 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).

Heureusement il est possible de contourner cette sécurité.

Méthode de récupération d’une instance :

private static Unsafe getUnsafeInstance() throws SecurityException,
NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
   Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
   theUnsafeInstance.setAccessible(true);
   return (Unsafe) theUnsafeInstance.get(Unsafe.class);
}

Autres cas d’utilisation de la classe :
Cette classe permet de connaître l’adresse d’un objet stocké en mémoire.
Plus précisément la position d’un champ dans la zone mémoire allouée aux instances de la classe.

// 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");

NB : qui a dit qu’il fallait obligatoirement un setter public ?

MemoryMappedFile

Le contenu du fichier est mappé dans l’espace mémoire du process depuis le fichier source. Une fois le fichier chargé (ou une portion du fichier) tous les accès se font en mémoire.
Il en résulte des performances extrêmes par rapport aux méthodes traditionnelles qui utilisent les accès disques.

Cette méthode est la plus indiquée en cas de copie partielle ou totale d’un fichier.


File file = new File(path);

// Creation d'un memory-mapped file en lecture seule
FileChannel roChannel = new RandomAccessFile(file, "r").getChannel();

MappedByteBuffer mbb = roChannel.map(FileChannel.MapMode.READ_ONLY, 0,
   (int) roChannel.size());

// tableau de bits utilisé pour la lectre
ByteArrayBuffer bits = new ByteArrayBuffer();

while(mbb.hasRemaining()) {
   // on lit le prochain octet
   byte b = mbb.get();
   if (b == 10 || b == 13) {
      if (!firstLine) {
         // On ignore la première ligne (entête)
         firstLine = true;
      } else {
         // on affiche le contenu du fichier
         System.out.println(bits.toString());
      }

     // on passe à une nouvelle ligne
     // on réinitialise le tableau
     bits = new ByteArrayBuffer();
     } else
       // on ajoute l'octet au tableau
       bits.write(b);
}

// on ferme le canal
roChannel.close();

// on vide le buffer
mbb.clear();
...

Particularités :

  • L’OS charge/décharge le contenu du fichier selon les besoins.
  • Utile pour les fichiers extrêmement volumineux.
  • Ou à l’inverse lorsque seul un fragment d’un fichier est utilisé.

Exemple

Mise en cache d’un extrait de la liste des communes mondiales de plus de 1000 habitants (+/- 114000) à partir d’un csv. Le code présenté a été teste avec la JVM OpenJDK 1.7 sur MAC (elle apporte un MBean de monitoring de la mémoire allouée par AllocateDirect()).

Quatre versions :

  1. Une classique avec une Map
  2. Une directbuffer en utilisant serialisation java
  3. Une directbuffer en utilisant externalizable java
  4. Une unsafe

1)

public class MemoryCache {

private Mapdata = new HashMap();

public void put(Object key, Commune object) throws IOException {
data.put(key, object);
}

public Object get(Object key) throws ClassNotFoundException, IOException {
return data.get(key);
}
…

}

2) & 3)

public class NativeMemoryCache {
private Map data = new HashMap();

public void put(Object key, Serializable object) throws IOException {
 byte[] bytes = serialize(object);
 // allocate native memory to store our object
 ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
 buf.put(bytes);
 buf.flip();
 data.put(key, buf);
}

public Object get(Object key) throws ClassNotFoundException, IOException {
 ByteBuffer buf = data.get(key).duplicate();
 byte[] bytes = new byte[buf.remaining()];
 buf.get(bytes);
 return deserialize(bytes);
}
…

}

4)

public class NativeMemoryCacheUnSafe {

/**
* Création de la map pour stocker la clé
* ainsi que l'adresse de l'objet en mémoire native
*/
private Map data = new HashMap(120000);

/**
* Objet Unsafe pour accéder
* à la mémoire native
*/
private static Unsafe unsafe = null;

/**
* Offset de stockage dans la mémoire native
*/
private static Long offset = new Long(0);

static {
 try {
   unsafe = getUnsafeInstance();
 } catch (SecurityException e) {
   System.out.println("Exception de sécurité :" + e);
 } catch (NoSuchFieldException e) {
   System.out.println("Impossible d'accéder au champ de la classe Unsafe :" + e);
 } catch (IllegalAccessException e) {
   System.out.println("Impossible d'accéder à la classe Unsafe :" + e);
 }
}

public void put(Long key, long codeCommune, String nomCommune, double latitude,
double longitude, String codePays, String codeRegion, String codeAdministratif,
int population, String timeZone) throws IOException,
SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
//Taille max d'un objet (bytes)
Long allocateMemory = unsafe.allocateMemory(400);

unsafe.putObject(allocateMemory, offset, new Commune(codeCommune, nomCommune, latitude,
 longitude, codePays, codeRegion, codeAdministratif, population, timeZone));

data.put(key, allocateMemory);
}

/**
* @param key identifiant unique de l'objet
* @return
* @throws ClassNotFoundException
* @throws IOException
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public Commune get(Object key) throws ClassNotFoundException, IOException,
SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {

 Long adresse = data.get(key);
 return (Commune)unsafe.getObject(adresse, offset);
}

…

}

Résultats

used : La quantité de mémoire réellement utilisée par l’application.
committed : La quantité de mémoire qui a été réservée auprès du système d’exploitation.

Paramètres JVM : -X:+AggressiveHeap

Test 1.

Type de test Classic
Temps de traitement (ms) 477
Collection Count  0
 Collection Time  0
Utilisation heap used=229749Ko
committed=2636992Ko
Utilisation non Heap used=3729Ko
committed = 23744Ko
Buffer pool
(si pas de libération de la mémoire)
_

Test 2.

Type de test Direct
(Serializable)
Temps de traitement (ms) 1697
Collection Count  1
 Collection Time 1329
Utilisation heap used=794123Ko
committed=2652352Ko
Utilisation non Heap used=4615Ko
committed=23744Ko
Buffer pool
(si pas de libération de la mémoire)
Type : Direct
Count:113576
MemoryUsed:31849106
Total capacity:31849106

Test 3.

Type de test Direct
(Externalizable)
Temps de traitement (ms) 1473
Collection Count 1
 Collection Time 413
Utilisation heap used=288094Ko
committed=2655296Ko
Utilisation non Heap used=4587Ko
committed = 23744Ko
Buffer pool
(si pas de libération de la mémoire)
Type : Direct
Count:113576
MemoryUsed:16175618
Total capacity:16175618

Test 4.

Type de test Direct
(Unsafe)
Temps de traitement (ms) 490
Collection Count 0
 Collection Time 0
Utilisation heap used=232327Ko
committed=2652480Ko
Utilisation non Heap used=3780Ko
committed=23744Ko
Buffer pool
(si pas de libération de la mémoire)
-

Frameworks utilisant l’accés direct à la mémoire

Terracota BigMemory (ehcache) : stockage de données dans la mémoire native
Coherence : Coherence propose le stockage off heap dans son cache
DirectMemory : Equivalent open source de Big Memory
Huge collections : Framework permettant de manipuler de grande collection en utilisant la mémoire native

Références

Java virtual machine specifications : http://java.sun.com/docs/books/jvms/
The Structure of the Java Virtual Machine : http://java.sun.com/docs/books/vmspec/2nd-edition/html/Overview.doc.html

Options de la JVM relatives à la mémoire directe.

-d64
Permet de passer la JVM en mode 64bits, utile pour adresser plus de mémoire.

-XX:MaxDirectMemorySize= ou -Dsun.nio.MaxDirectMemorySize=
Permet de définir la mémoire maximale réservées pour la mémoire off heap.

-XX:+PageAlignDirectMemory ou -Dsun.nio.PageAlignDirectMemory=true
Permet de s’assurer qu’un espace de mémoire native est alignée sur une page mémoire. Avant le JDK7, une page entière (ou plus) était allouée quelle que soit la taille de l’objet (4096 octets sur une majorité d’OS).

Conclusion

L’exemple retenu n’est pas le plus favorable aux cas d’utilisation de la mémoire off-heap, c’est pourquoi il met en exergue les inconvénients de cette solution.

L’utilisation de la mémoire native est une solution à envisager si votre programme est très consommateur en mémoire et pas forcement pour les programmes affectés par les passages du GC.

Ce n’est donc pas forcement le miracle attendu dans le sens ou ne vous dispensera pas des tracas du GC.

De plus le programme consomme temporairement autant de heap qu’un programme traditionnel (creation d’objets avant de stocker off-heap).

Enfin la sérialisation est coûteuse en terme de performances (mais seuls les programmes les plus exigeants la verront comme un véritable obstacle).
C’est pourquoi, sauf à utiliser la classe Unsafe, l’utilisation de la mémoire native est moins performante que son homologue Heap.

Conditions idéales d’utilisation de la mémoire native :

  • Idéal quand la volumétrie de données est très importante (> 2 Go).
  • Quand les données sont stables dans le temps (sinon les données sont collectées par le GC).
  • Et enfin quand les données sont sérialisables.

L’utilisation des classes comme Unsafe est très dangereuse et réclamera une longue mise au point afin d’éviter un crash de la JVM avec pour seuls indices un code retour 139 (Segmentation violation) ou 134 (Dump core).
Pour les programmes affectés par le passage du Garbage Collector, c’est une solution parmi d’autres :

1. Tuning de la JVM et du GC
2. Création d’un pool d’objets réutilisables (crées à l’avance et jamais libérés).

Le code source est disponible ici : Source

TwitterFacebookGoogle+LinkedIn
  • http://twitter.com/bdissert Benoît Dissert

    Bonjour,

    Excellent article sur un sujet peu abordé. Merci.

    Cela dit, je n’aime pas la phrase “il faut privilégier la mémoire aux autres types d’accès”.
    C’est un possibilité parmi d’autres, elle a ses avantages et ses inconvénients.
    Et, en particulier, ce n’est pas l’espace de stockage temporaire le plus rapide. Ainsi que l’explique, par exemple Jevgeni Kabanov, par rapport aux accès aux données en registre les accès des données en mémoire sont infiniment plus lents. 

    (cf http://blog.zenika.com/index.php?post/2011/06/07/Do-you-really-get-memory-Jevgeni-Kabanov)

    D’où l’importance de ne pas mettre les membres volatiles, d’avoir des données immutables, …

    • cparageaud

      Bonjour,
      concernant les accès RAM versus accès registre du processeur, je ne peux qu’approuver (je l’ai moi même évoqué dans mon blog sur l’architecture LMAX).Mais pour moi il s’agit d’un problème différent de celui évoqué ici qui serait, comment stocker 100Go de données ?

      • http://twitter.com/bdissert Benoît Dissert

        Tout à fait.

        C’est donc bien dans un contexte (beaucoup de données, pas de persistance) que cette solution est pertinente. On est d’accord.

        J’ai relu l’article au calme, en détail hier soir. J’aurais trouvé pertinent de mettre un avertissement à destination des plus jeunes de vos lecteurs pour prévenir qu’il y avait du code à ne pas forcément reproduire dans votre article (usage de sun.misc.Unsafe ou setAccessible(true)) sans avoir fait signer une décharge par leur architecte :-)

  • Brice Dutheil

    Article intéressant. Cela dit je ne recommanderait à personne de se jeter dans ce genre de code, à moins d’avoir vraiment de très bonnes raisons, que toute les autres tentatives d’optimisation ont échouées, et que en en plus les librairies dispo n’ont pas résolu les problèmes.

    Autre chose aussi. Je ne crois pas que la stack d’une méthode soit nettoyée par le GC, par nature ça ne peut pas fonctionner. La stack est un grand tableau sur lequel on empile des choses, et le pointeur d’instruction se balade sur ce tableau pour indiquer ou il en est. En revanche la JVM utilise l’allocation par thread-local (qui est une partie de la heap) et celle-ci est donc gérée par le GC. Par contre le GC fait bien attention aux références qui sont utilisées sur la stack.
    Enfin grace à l’escape analysis la JVM peut se permettre d’allouer un objet sur la stack (et donc hors heap). Ce genre d’allocation n’a virtuellement aucun coût pour l’allocation même et pour la désallocation.

    • cparageaud

      Bonjour,

      même si je partage les conseils de prudence concernant l’utilisation de la mémoire directe, il faut savoir que nous l’utilisons tous les jours au travers de frameworks.

      Concernant la stack et le Garbage Collector je vais remplacer la phrase ”Cette zone est nettoyée par le GC” par ”Cette zone est parcourue par le GC afin d’identifier les objets non référencés”.

      • Brice Dutheil

        Quand je disais ce lancer dans ce code je faisais plus référence, comme Benoit l’a fait remarquer, à coder quelque chose qui utilise le fameux Unsafe ;)

  • killbulle

    super article 
    un point cepandant que je comprends pas
    “C’est pourquoi, malgré la multiplication des architectures et OS 64 bits, cette barrière n’a que peut évoluée depuis les débuts de Java.
    On peut la fixer à 3-4 Go.”  ?rien n’interdit d’avoir des piles largement supérieure a plusieurs giga !
    ces techniques sont plutôt destinées pour des objects long live ou très gros que on veut pas parcourir lors des majors collection non ?(donc surement corrélé au gc non generationnel uniquement, pas le G1) ?

    • Marco Godin

      (donc surement corrélé au gc  generationnel uniquement, pas le G1) ?

      • Marco Godin

        je me corrige le G1est genrationnel(je pensais au decoupage) mais je pense que tous les old zone ne sont pas systèmatiquement parcourus

  • http://www.drazzib.com/ Damien Raude-Morvan

    Comme autre outil important qui utilise des accès directs à la mémoire, on peut citer Cassandra : en effet, le cache contenant les lignes récemment chargées est stocké “off-heap” :
    http://www.datastax.com/dev/blog/whats-new-in-cassandra-1-0-improved-memory-and-disk-space-management