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é?