JNA / ByteBuffer ne pas avoir libéré et provoquant tas C à manquer de mémoire

StackOverflow https://stackoverflow.com/questions/1744533

  •  20-09-2019
  •  | 
  •  

Question

Permettez-moi de commencer en disant que ma compréhension de la façon dont la JNA et Java directement les allocations de mémoire natifs est viscéral au mieux, donc je suis en train de décrire ma compréhension de ce qui se passe. Toute correction en plus des réponses serait génial ...

Je suis en cours d'exécution d'une application qui mixe Java et C du code natif en utilisant la JNA et je suis en cours d'exécution accross un problème reproductible avec le Garbage Collector Java ne pas les références libres pour diriger les allocations de mémoire natives, ce qui dans le tas de C à court de mémoire.

Je suis positif que mon application C est pas la source du problème d'allocation, comme je passe un java.nio.ByteBuffer dans mon code C, modifier le tampon, puis d'accéder au résultat dans ma fonction Java. J'ai un seul malloc et un seul free correspondant au cours de chaque appel de fonction, mais après plusieurs reprises l'exécution du code en Java malloc finira par échouer.

Voici un ensemble quelque peu banalisé de code qui présente la question -. de façon réaliste, je suis en train d'allouer environ 16-32MB sur le tas C lors de l'appel de fonction

Mon code Java fait quelque chose comme:

public class MyClass{
    public void myfunction(){
        ByteBuffer foo = ByteBuffer.allocateDirect(1000000);
        MyDirectAccessLib.someOp(foo, 1000000);
        System.out.println(foo.get(0));
    }
}

public MyDirectAccessLib{
    static {
        Native.register("libsomelibrary");
    }
    public static native void someOp(ByteBuffer buf, int size);
}

Alors mon code C pourrait être quelque chose comme:

#include <stdio.h>
#include <stdlib.h>
void someOp(unsigned char* buf, int size){
    unsigned char *foo;
    foo = malloc(1000000);
    if(!foo){
        fprintf(stderr, "Failed to malloc 1000000 bytes of memory\n");
        return;
    }
    free(foo);

    buf[0] = 100;
}

Le problème est après avoir appelé cette fonction à plusieurs reprises le tas Java est un peu stable (il pousse lentement), mais la fonction C peut éventuellement allouer plus de mémoire. À un niveau élevé, je crois que c'est parce que Java est allouez de la mémoire sur le tas C, mais pas nettoyer les ByteBuffer qui pointe à cette mémoire parce que l'objet Java ByteBuffer est relativement faible.

Jusqu'à maintenant, je l'ai trouvé courir le GC manuellement dans ma fonction fournira le nettoyage nécessaire, mais cela semble être à la fois une mauvaise idée et une mauvaise solution.

Comment puis-je gérer ce problème mieux afin que l'espace ByteBuffer est libéré de façon appropriée et mon espace de tas C est contrôlé?

Je crois comprendre du problème incorrect (y at-il quelque chose que je suis en mal)?

Modifier : la taille des tampons ajustés pour mieux refléter ma demande réelle, je l'allocation pour les images d'environ 3000x2000 ...

Était-ce utile?

La solution

Je pense que vous avez correctement diagnostiqué: vous ne manquerez jamais de tas de Java, de sorte que la machine virtuelle Java ne sont pas des déchets recueillir, et les tampons mis en correspondance ne sont pas libérés. Le fait que vous n'avez des problèmes lors de l'exécution GC semble manuellement pour confirmer. Vous pouvez également activer la journalisation de collection bavard comme une confirmation secondaire.

Alors, que pouvez-vous faire? Eh bien, la première chose que je vais essayer est de garder la taille du tas JVM initiale petite, en utilisant l'argument de ligne de commande -Xms. Cela peut causer des problèmes, si votre programme alloue en permanence la mémoire de petites quantités sur le tas Java, comme il fonctionnera GC plus fréquemment.

J'utilise aussi le pmap outil (ou quel que soit son équivalent est sous Windows) pour examiner la carte de mémoire virtuelle. Il est possible que vous fragmenter le tas C, en allouant des tampons de taille variable. Si tel est le cas, alors vous verrez une carte virtuelle chaque plus grande, avec des écarts entre les blocs « Anon ». Et la solution, il est d'allouer des blocs de taille constante qui sont plus grandes que vous avez besoin.

Autres conseils

Vous êtes face à réellement un bogue connu dans la machine virtuelle Java . La meilleure solution figurant dans le rapport de bogue est:

  • "Le -XX:. MaxDirectMemorySize = option peut être utilisée pour limiter la quantité de mémoire directe utilisée Une tentative d'allouer de la mémoire directe qui causerait cette limite à dépasser provoque une CG complète de manière à provoquer le traitement de référence et la libération de tampons "déréférencées.

D'autres solutions de contournement possibles sont les suivantes:

  • Insérer invocations System.gc () explicites occasionnels afin d'assurer que les tampons directs sont remis en état.
  • Réduire la taille de la jeune génération à la force GCS plus fréquents.
  • piscine Explicitement tampons directs au niveau de l'application.

Si vous voulez vraiment compter sur des tampons d'octets directs, alors je suggère la mise en commun au niveau de l'application. En fonction de la complexité de votre application, vous pouvez même simplement en cache et réutiliser le même tampon (méfiez-vous de plusieurs threads).

Je soupçonne que votre problème est dû à l'utilisation de directs tampons d'octets. Ils peuvent être attribués en dehors du tas Java.

Si vous appelez la méthode fréquemment et allouer de petits tampons à chaque fois, votre modèle d'utilisation est probablement pas un bon moyen pour un tampon direct.

Afin d'isoler le problème, je passe à un (Java) la mémoire tampon allouée (il suffit d'utiliser la méthode allocate en place de allocateDirect. Si cela fait votre problème de mémoire aller plus loin, vous avez trouvé le coupable. la question suivante est de savoir si un directement tampon d'octets a un avantage en terme de performance. Dans le cas contraire (et je suppose que cela ne fonctionne pas), alors vous aurez pas besoin de vous soucier de la façon de nettoyer il correctement.

Si vous manquez de mémoire tas, un GC est déclenché automatiquement. Toutefois, si vous manquez de mémoire directe, le GC ne se déclenche pas (sur la JVM de Sun au moins) et vous juste obtenir un OutOfMemoryError même si un GC libérerait suffisamment de mémoire. J'ai trouvé que vous devez déclencher un GC manuellement dans cette situation.

Une meilleure solution peut être de réutiliser la même ByteBuffer afin que vous ne devez jamais re-acllocate ByteBuffers.

Pour libérer de Buffer directe [ 1 ] mémoire, vous pouvez utiliser 3 ] JNI 6 API peut être utilisé pour l'acquisition de pointeur sur la mémoire pour la Buffer puis la commande standard free(void *ptr) sur le pointeur pour libérer la mémoire.

Au lieu d'écrire du code tel que C pour appeler ladite fonction de Java, vous pouvez utiliser JNA 's Native.getDirectBufferPointer(Buffer) [ 6 ]

La seule chose qui reste après est de renoncer à toutes les références à l'objet Buffer. La collecte des ordures de Java alors libérer l'instance Buffer comme il le fait avec tout autre objet non référencé.

S'il vous plaît noter que Buffer directe ne correspond pas nécessairement 1: 1 à une zone de mémoire allouée. Par exemple API JNI a NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) [ 7 ] . En tant que tel, vous ne devez libérer de la mémoire de celui des Buffer, dont la région allocation de mémoire que vous connaissez l'un à un avec la mémoire native.

Je aussi ne sais pas si vous pouvez libérer un Buffer direct créé par le ByteBuffer.allocateDirect(int) Java [ 8 ] pour exactement la même raison que ci-dessus. Il pourrait être mise en œuvre de la plate-forme Java JVM ou des détails spécifiques qu'ils utilisent la piscine ou faire 1:. 1 allocations de mémoire lors de la remise de nouveaux Buffers directs

suite Voici un extrait légèrement modifié de ma bibliothèque en ce qui concerne ByteBuffer directe [ 9 ] manutention (utilise Native JNA [ 10 ] et Pointer [ 11 ] classes):

/**
 * Allocate native memory and associate direct {@link ByteBuffer} with it.
 * 
 * @param bytes - How many bytes of memory to allocate for the buffer
 * @return The created {@link ByteBuffer}.
 */
public static ByteBuffer allocateByteBuffer(int bytes) {
        long lPtr = Native.malloc(bytes);
        if (lPtr == 0) throw new Error(
            "Failed to allocate direct byte buffer memory");
        return Native.getDirectByteBuffer(lPtr, bytes);
}

/**
 * Free native memory inside {@link Buffer}.
 * <p>
 * Use only buffers whose memory region you know to match one to one
 * with that of the underlying allocated memory region.
 * 
 * @param buffer - Buffer whose native memory is to be freed.
 * The class instance will remain. Don't use it anymore.
 */
public static void freeNativeBufferMemory(Buffer buffer) {
        buffer.clear();
        Pointer javaPointer = Native.getDirectBufferPointer(buffer);
        long lPtr = Pointer.nativeValue(javaPointer);
        Native.free(lPtr);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top