41 votes

Devrait Java finaliseur vraiment être évité également pour les natifs des objets de pair gestion du cycle de vie?

Dans mon expérience en tant que C++/Java/Android développeur, je viens d'apprendre que les finaliseurs sont presque toujours une mauvaise idée, la seule exception étant la gestion d'un "natif par les pairs" objet requis par le code java à l'appel de code C/C++ via JNI.

Je suis conscient de la JNI: bien gérer la durée de vie d'un objet java question, mais cette question aborde les raisons de ne pas utiliser un finaliseur de toute façon, ni pour les pairs. C'est donc une question/discussion sur un rejet de la réponse à la question susmentionnée.

Joshua Bloch dans son Effectif Java explicitement les listes de ce cas comme une exception à la renommée de son avis sur n'utilisant pas les finaliseurs:

Une deuxième utilisation légitime des finaliseurs préoccupations des objets avec des camarades du pays d'accueil. Un natif par les pairs est un objet natif à laquelle un objet normal délégués via des méthodes indigènes. Parce qu'un natif par les pairs n'est pas un objet normal, le garbage collector ne la connaissent pas et ne peut pas la récupérer lors de son langage de programmation Java par les pairs est récupérée. Un finaliseur est un véhicule approprié pour l'exécution de cette tâche, en supposant que le natif de pairs détient pas de ressources critiques. Si le natif de pairs détient des ressources qui doit être close dans les moindres délais, la classe doit être explicitement la résiliation de la méthode, comme décrit ci-dessus. La résiliation de la méthode doit faire tout ce qui est nécessaire pour libérer la ressource critique. La résiliation méthode peut être une méthode native, ou il peut invoquer un.

(Voir aussi "Pourquoi est-ce la version finale de la méthode incluse dans Java?" question sur stackexchange)

Alors j'ai regardé le vraiment intéressant Comment gérer la mémoire natif Android, et de parler à la Google I/O '17, où Hans Boehm en fait les défenseurs contre l'utilisation des outils de finalisation pour gérer natif pairs d'un objet java, également citant Efficace Java comme une référence. Après avoir rapidement de mentionner pourquoi explicite supprimer de la patrie, de pairs ou de la fermeture automatique basé sur le champ d'application pourrait ne pas être une alternative viable, il a des conseils à l'aide de java.lang.ref.PhantomReference à la place.

Il fait quelques points intéressants, mais je ne suis pas complètement convaincu. Je vais essayer de courir à travers certains d'entre eux et l'état de mes doutes, en espérant que quelqu'un peut jeter plus de lumière sur eux.

À partir de cet exemple:

class BinaryPoly {

    long mNativeHandle; // holds a c++ raw pointer

    private BinaryPoly(long nativeHandle) {
        mNativeHandle = nativeHandle;
    }

    private static native long nativeMultiply(long xCppPtr, long yCppPtr);

    BinaryPoly multiply(BinaryPoly other) {
        return new BinaryPoly ( nativeMultiply(mNativeHandle, other.mNativeHandler) );
    }

    // …

    static native void nativeDelete (long cppPtr);

    protected void finalize() {
        nativeDelete(mNativeHandle);
    }
}

Lorsqu'une classe java contient une référence à un natif par les pairs qui est supprimé dans la méthode finalizer, Bloch répertorie les limites d'une telle approche.

Les finaliseurs pouvez exécuter dans un ordre arbitraire

Si deux objets deviennent inaccessibles, les finaliseurs fait exécuter dans un ordre arbitraire, qui inclut le cas lorsque deux objets qui le point les uns les autres à devenir inaccessible en même temps, ils peuvent être mis au point dans le mauvais ordre, ce qui signifie que la seconde pour être finalisé en fait essaie d'accéder à un objet qui a déjà été finalisé. [...] Comme un résultat que vous pouvez obtenir en balançant les pointeurs et voir libéré des objets en c++ [...]

Et à titre d'exemple:

class SomeClass {
    BinaryPoly mMyBinaryPoly:
    …
    // DEFINITELY DON'T DO THIS WITH CURRENT BinaryPoly!
    protected void finalize() {
        Log.v("BPC", "Dropped + … + myBinaryPoly.toString());   
    }
}

Ok, mais n'est-ce pas vrai aussi si myBinaryPoly est un pur objet Java? Si je comprends bien , le problème vient de l'exploitation sur une éventuellement finalisé objet à l'intérieur de son propriétaire du finalizer. Dans le cas où nous sommes seulement à l'aide de l'outil de finalisation d'un objet à supprimer de sa propre salle de natif de par les pairs et de ne rien faire d'autre, on devrait être bon, non?

Finaliseur peut être invoquée alors que la méthode native est jusqu'à l'exécution de

Par Java règles, mais pas actuellement sur Android:
Objet x du finalizer peut être invoquée alors que l'un de x méthodes est toujours en cours d'exécution, et d'accéder à l'objet natif.

Pseudo-code de ce multiply() sera compilé est montré pour expliquer cela:

BinaryPoly multiply(BinaryPoly other) {
    long tmpx = this.mNativeHandle; // last use of "this"
    long tmpy = other.mNativeHandle; // last use of other
    BinaryPoly result = new BinaryPoly();
    // GC happens here. "this" and "other" can be reclaimed and finalized.
    // tmpx and tmpy are still neeed. But finalizer can delete tmpx and tmpy here!
    result.mNativeHandle = nativeMultiply(tmpx, tmpy)
    return result;
}

C'est effrayant, et je suis vraiment soulagée, ce n'est pas le cas sur android, parce que ce que je comprends, c'est qu' this et other obtenir des ordures collectées avant d'aller hors de portée! C'est encore plus étrange considérant qu' this est l'objet de la méthode est appelée, et qu' other est l'argument de la méthode, de sorte qu'ils devraient déjà "être vivant" dans le domaine dans lequel la méthode est appelée.

Une première solution rapide à ce problème serait de les appeler mannequin méthodes sur les deux this et other (laid!), ou en les passant à la méthode native (où l'on peut ensuite récupérer l' mNativeHandle et les exploiter). Et d'attendre... this est déjà par défaut l'un des arguments de la méthode native!

JNIEXPORT void JNICALL Java_package_BinaryPoly_multiply
(JNIEnv* env, jobject thiz, jlong xPtr, jlong yPtr) {}

Comment peut - this être éventuellement ordures collectées?

Les finaliseurs peut être différé pendant trop longtemps

"Pour que cela fonctionne correctement, si vous exécutez une application qui alloue beaucoup de natifs de la mémoire et relativement peu de mémoire java il peut effectivement ne pas être le cas, que le garbage collector s'exécute assez rapidement pour invoquer les finaliseurs [...] donc en fait, vous pouvez avoir à invoquer Système.gc() et du Système.runFinalization() de temps à autre, ce qui est difficile à faire [...]"

Si le natif par les pairs n'est vue que par un seul objet java qui il est lié, n'est-ce pas fait transparente pour le reste du système, et donc le GC doit juste gérer le cycle de vie de l'objet Java que c'était un pur java un? Il y a clairement quelque chose que je n'arrive pas à le voir ici.

Les finaliseurs peut effectivement prolonger la durée de vie de l'objet java

[...] Parfois, les finaliseurs effectivement prolonger la durée de vie de l'objet java pour un autre cycle de nettoyage de mémoire, ce qui signifie pour les générations éboueurs ils peuvent effectivement causer de survivre dans l'ancienne génération et la durée de vie peut être considérablement étendue comme un résultat de tout avoir d'un outil de finalisation.

J'avoue ne pas vraiment quel est le problème ici, et comment il se rapporte à avoir un native par les pairs, je vais faire quelques recherches et éventuellement mettre à jour la question :)

En conclusion

Pour l'instant, je continue de croire que l'utilisation d'une sorte de RAII une approche à la maternelle par les pairs est créé dans le java constructeur de l'objet et à la suppression de la méthode finalize est pas réellement dangereux, à condition que:

  • le natif de par les pairs n'est pas une ressource critique (dans ce cas il devrait y avoir une méthode distincte pour la libération de la ressource, le natif de pairs ne doit agir comme l'objet java "homologue" dans le royaume d'origine)
  • le natif de par les pairs n'a pas de durée de threads ou de faire bizarre simultanées des trucs dans son destructeur (qui aurait envie de faire ça?!?)
  • le natif de pairs pointeur n'est jamais partagée à l'extérieur de l'objet java, n'appartient qu'à une seule instance, et accessible uniquement à l'intérieur de l'objet java de méthodes. Sur Android, un objet java peut accéder à la maternelle par les pairs d'une autre instance de la même classe, juste avant l'appel d'une jni méthode d'accepter autre natif pairs ou, mieux, juste de passage les objets java de la méthode native lui-même
  • la java finaliseur de l'objet ne supprime ses propres pairs, et ne fait rien d'autre

Est-il un autre restriction qui devrait être ajouté, ou il n'y a vraiment aucun moyen de s'assurer qu'un finaliseur est sûre, même avec toutes les restrictions d'être respecté?

10voto

Dmitry Timofeev Points 120

finalize et d'autres approches qui utilisent GC connaissance des objets de la vie ont un couple de nuances:

  • visibilité: pouvez-vous garantir que toutes les écritures des méthodes de l'objet o fait sont visibles à l'outil de finalisation (c'est à dire, il est un passe-avant la relation entre la dernière action sur l'objet o et le code d'exécution de finalisation)?
  • accessibilité: comment pouvez-vous garantir, qu'un objet o n'est pas détruit prématurément (par exemple, tandis que l'une de ses méthodes est en cours d'exécution), ce qui est permis par la JLS? Il ne se produire et provoquer des pannes.
  • commande: pouvez-vous respecter un certain ordre dans lequel les objets sont finalisés?
  • résiliation: avez-vous besoin de détruire tous les objets lorsque votre application se termine?

Il est possible de résoudre tous ces problèmes avec les finaliseurs, mais il nécessite une quantité décente de code. Hans-J. Boehm a une belle présentation qui montre ces problèmes et des solutions possibles.

Afin de garantir la visibilité, vous devez synchroniser votre code, c'est à dire, mettre opérations avec la Libération de la sémantique dans vos méthodes ordinaires, et une opération à Acquérir de la sémantique dans votre outil de finalisation. Par exemple:

  • D'un magasin dans un volatile à la fin de chaque méthode + lu de la même volatile dans un finaliseur.
  • La libération de verrou sur l'objet à la fin de chaque méthode + acquérir le verrou au début d'un finaliseur (voir keepAlive mise en œuvre dans Boehm diapositives).

Pour garantir l'accessibilité (quand ce n'est pas déjà garanti par la spécification de langue), vous pouvez utiliser:

La différence entre la plaine finalize et PhantomReferences , c'est que ce dernier vous donne beaucoup plus de contrôle sur les différents aspects de la rédaction:

  • Peut avoir plusieurs files d'attente de réception de fantôme refs et choisir un thread d'exécution de la finalisation de chacun d'eux.
  • Peut finaliser dans le même thread que celui qui n'a d'allocation (par exemple, le fil local ReferenceQueues).
  • Plus facile d'exécuter la commande: maintenir une forte référence à un objet B qui doit rester en vie lors de l' A est finalisé comme un champ d' PhantomReference de A;
  • Plus facile à mettre en œuvre sûr de résiliation, vous devez conserver PhantomRefereces fortement accessible jusqu'à ce qu'ils sont en file d'attente par GC.

5voto

Terraslate Points 66

Mon point de vue personnel que l'on doit libérer les objets natifs dès que vous avez terminé avec eux, d'une manière déterministe. En tant que tel, à l'aide étendue pour gérer leur est préférable de s'appuyer sur l'outil de finalisation. Vous pouvez utiliser l'outil de finalisation pour le nettoyage comme un dernier recours, mais, je ne voudrais pas utiliser uniquement pour gérer la durée de vie réelle pour les raisons que vous avez fait remarquer dans votre propre question.

En tant que tel, laissez-le finaliseur être la dernière tentative, mais pas la première.

4voto

Scott Points 86

Je pense que la plupart de ce débat provient de l'héritage statut de finalize(). Il a été introduit en Java à l'adresse de choses que la collecte des ordures n'a pas de couverture, mais pas nécessairement des choses comme le système de ressources (fichiers, les connexions réseau, etc.) donc, il a toujours senti sorte de demi-cuite. Je ne suis pas forcément d'accord avec l'aide de quelque chose comme phantomreference, qui prétend être la meilleure finaliseur que finalize() lorsque le modèle lui-même est problématique.

Hugues Moreau a souligné que finalize() sera obsolète dans Java 9. Le modèle préféré de l'équipe Java semble traiter les choses comme des pairs comme un système de ressources et de les nettoyer via try-with-resources. La mise en œuvre de AutoCloseable vous permet de faire cela. Notez que try-with-resources et AutoCloseable après jour à la fois Josh Bloch la participation directe du Java et Efficace Java 2nd edition.

1voto

wanpen Points 176

1voto

ferini Points 369

Comment cela peut-il être, éventuellement, de ces ordures?

Parce que la fonction nativeMultiply(long xCppPtr, long yCppPtr) est statique. Si une fonction native est statique, le second paramètre est jclass pointant vers sa classe au lieu d' jobject pointant vers this. Dans ce cas - this n'est pas un des arguments.

Si elle n'avait pas été statiques, il y aurait seulement d'un problème avec l' other objet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X