Question

Lorsque je construis un objet java à l'aide de méthodes JNI afin de le transmettre en tant que paramètre à une méthode java invoquée à l'aide de l'API d'appel JNI, comment gérer sa mémoire?

Voici ce avec quoi je travaille:

J'ai un objet C dont la méthode de destruction est plus complexe que free () . Cet objet C doit être associé à un objet Java et, une fois l'application terminée, je n'ai plus besoin de l'objet C.

Je crée l'objet Java de la manière suivante (vérification des erreurs pour plus de clarté):

c_object = c_object_create ();
class = (*env)->FindClass (env, "my.class.name");
constructor = (*env)->GetMethodID (env, class, "<init>", "(J)V");
instance = (*env)->NewObject (env, class, constructor, (jlong) c_object);

method = (*env)->GetMethodID (env, other_class, "doSomeWork", "(Lmy.class.name)V");
(*env)->CallVoidMethod (env, other_class, method, instance);

Alors, maintenant que j'en ai terminé avec instance , que dois-je en faire? Idéalement, j'aimerais laisser le ramassage des ordures à la machine virtuelle; quand ce sera fait avec instance , il serait fantastique de pouvoir également appeler c_object_destroy () sur le pointeur que je lui ai fourni. Est-ce possible?

Une question distincte, mais liée, concerne la portée des entités Java que je crée dans une méthode comme celle-ci; Dois-je libérer manuellement, par exemple, classe , constructeur ou méthode ci-dessus? Le document de la JNI est extrêmement vague (à mon avis) au sujet de la gestion adéquate de la mémoire.

Était-ce utile?

La solution

Il existe plusieurs stratégies pour récupérer les ressources natives (objets, descripteurs de fichier, etc.)

  1. Invoquez une méthode JNI lors de finalize () qui libère la ressource. Certaines personnes recommandent de ne pas implémenter de finaliser , et vous ne pouvez pas vraiment vous en assurer. que votre ressource native est toujours libérée. Pour les ressources telles que la mémoire, ce n’est probablement pas un problème, mais si vous avez un fichier par exemple qui doit être vidé à une heure prévisible, finalize () n’est probablement pas une bonne idée.

  2. Invoquez manuellement une méthode de nettoyage. Ceci est utile si vous savez à un moment donné que la ressource doit être nettoyée. J'ai utilisé cette méthode lorsque j'avais une ressource qui devait être désallouée avant de décharger une DLL dans le code JNI. Afin de permettre le rechargement ultérieur de la DLL, je devais m'assurer que l'objet était vraiment désalloué avant de tenter de décharger la DLL. En utilisant seulement finalize (), je n’aurais pas obtenu cette garantie. Ceci peut être combiné avec (1) pour permettre à la ressource d'être allouée au cours de finalize () ou à la méthode de nettoyage appelée manuellement. (Vous avez probablement besoin d’une carte canonique de WeakReferences pour savoir quels objets doivent faire appel à leur méthode de nettoyage.)

  3. Soi-disant, la référence fantôme peut également être utilisée pour résoudre ce problème, mais je ne sais pas exactement comment une telle solution fonctionnerait.

En fait, je suis en désaccord avec vous sur la documentation JNI. Je trouve la spécification JNI exceptionnellement clair sur la plupart des questions importantes, même si les sections sur la gestion des références locales et mondiales auraient pu être plus élaborées.

Autres conseils

Les spécifications JNI traitent de la question de savoir qui est le propriétaire de " Objets Java créés à l'aide de méthodes JNI ici . Vous devez faire la distinction entre les références locales et mondiales .

Lorsque la machine virtuelle Java appelle un code JNI en code natif, elle configure un registre pour garder une trace de tous les objets créés pendant l'appel. Tout objet créé lors de l’appel natif (c’est-à-dire renvoyé d’une fonction d’interface JNI) est ajouté à ce registre. Les références à ces objets sont appelées références locales . Lorsque la méthode native retourne à la machine virtuelle Java, toutes les références locales créées lors de l'appel de la méthode native sont détruites. Si vous passez des appels à la machine virtuelle Java pendant un appel de méthode native, la référence locale sera toujours active lorsque le contrôle reviendra à la méthode native. Si la machine virtuelle Java appelée à partir du code natif appelle à nouveau le code natif, un nouveau registre de références locales est créé et les mêmes règles s'appliquent.

(En fait, vous pouvez implémenter votre propre exécutable JVM (c.-à-d. java.exe) à l'aide de l'interface JNI, en créant une JVM (recevant ainsi un pointeur JNIEnv *), en recherchant la classe indiquée sur la ligne de commande, et en invoquant la méthode main () dessus.)

Toutes les références renvoyées par les méthodes d'interface JNI sont locales . Cela signifie qu'en temps normal, il n'est pas nécessaire de désallouer manuellement les références renvoyées par les méthodes JNI, car elles sont détruites lors du retour vers la JVM. Parfois, vous souhaitez toujours les détruire "prématurément", par exemple lorsque vous devez supprimer un grand nombre de références locales avant de revenir à la machine virtuelle.

Les références globales sont créées (à partir de références locales) à l'aide de NewGlobalRef (). Ils sont ajoutés à un registre spécial et doivent être désalloués manuellement. Les références globales ne sont utilisées que pour les objets Java pour lesquels le code natif doit contenir une référence sur plusieurs appels JNI, par exemple si vous avez des événements déclencheurs de code natif qui doivent être renvoyés à Java. Dans ce cas, le code JNI doit stocker une référence à un objet Java devant recevoir l'événement.

J'espère que cela clarifie un peu le problème de gestion de la mémoire.

Objet: "Une question distincte, mais liée" ... vous n'avez pas besoin de libérer manuellement jclass, jfieldID et jmethodID lorsque vous les utilisez dans un "local". le contexte. Toute référence d'objet réelle obtenue (pas jclass, jfieldID, jmethodID) doit être libérée avec DeleteLocalRef.

Le GC va collecter votre instance, mais il ne libèrera pas automatiquement la mémoire de segment non java allouée dans le code natif. Vous devriez avoir une méthode explicite dans votre classe pour libérer l’instance de c_object.

C’est l’un des cas où je vous recommanderais d’utiliser un finaliseur pour vérifier si c_object a été publié et le publier, en enregistrant un message si ce n’est pas le cas.

Une technique utile consiste à créer une instance Throwable dans le constructeur de la classe Java et à la stocker dans un champ (ou tout simplement à initialiser le champ en ligne). Si le finaliseur détecte que la classe n'a pas été correctement supprimée, il imprimera le chemin de pile en indiquant la pile d'allocation.

Une suggestion est d'éviter de faire directement JNI et d'aller avec gluegen ou Swig (tous deux génèrent du code et peuvent être liés statiquement).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top