2010-07-29 32 views
6

J'ai lu these slides sur les finaliseurs Java. Dans celui-ci, l'auteur décrit un scénario (sur la diapositive 33) par lequel CleanResource.finalize() pourrait être exécuté par le thread finalizer tandis que CleanResource.doSomething() est toujours en cours d'exécution sur un autre thread. Comment cela pourrait-il arriver?Java GC Question: Comment un objet peut-il devenir inaccessible alors que l'une de ses méthodes est encore en cours d'exécution?

Si doSomething() est une méthode non-statique, alors pour exécuter cette méthode quelqu'un, quelque part doit avoir une référence forte à cela ... non? Alors, comment cette référence pourrait-elle être effacée avant que la méthode ne retourne? Un autre thread peut-il basculer et annuler cette référence? Si cela se produisait, doSomething() reviendrait-il normalement sur le fil d'origine?

C'est tout ce que je veux vraiment savoir, mais pour un vraiment ci-dessus et au-delà de réponse, vous pouvez me dire pourquoi le doSomething() sur la diapositive 38 est mieux que le doSomething() sur la diapositive 29. Pourquoi est-il suffisant pour simplement invoquer cette méthode keepAlive()? N'auriez-vous pas besoin d'envelopper l'appel entier à myImpl.doSomething() dans un bloc synchronized(this){}?

Répondre

3

EDIT3:

Le résultat est que le finaliseur et une méthode régulière peuvent être exécutées simultanément sur la même instance. Voici une explication de comment cela peut arriver. Le code est essentiellement:

class CleanResource { 
    int myIndex; 
    static ArrayList<ResourceImpl> all; 

    void doSomething() { 
    ResourceImpl impl = all.get(myIndex); 
    impl.doSomething(); 
    } 

    protected void finalize() { ... } 
} 

Vu le code client:

CleanResource resource = new CleanResource(...); 
resource.doSomething(); 
resource = null; 

Cela pourrait être JITed à quelque chose comme ce pseudo C

register CleanResource* res = ...; call ctor etc.. 
// inline CleanResource.doSomething() 
register int myIndex = res->MyIndex; 
ResourceImpl* impl = all->get(myInddex); 
impl->DoSomething(); 
// end of inline CleanResource.doSomething() 
res = null; 

Exécuté comme ça, res est effacé après la Inline CleanResource.doSomething() est fait, donc le gc ne se produira qu'après que la méthode ait fini d'être exécutée. Il n'y a aucune possibilité de finaliser l'exécution concurremment avec une autre méthode d'instance sur la même instance.

Mais, l'écriture à res n'est pas utilisé après ce moment-là, et étant donné qu'il n'y a pas de clôtures, il peut être déplacé plus tôt dans l'exécution, immédiatement après l'écriture:

register CleanResource* res = ...; call ctor etc.. 
// inline CleanResource->doSomething() 
register int myIndex = res->MyIndex; 
res = null; /// <----- 
ResourceImpl* impl = all->get(myInddex); 
impl.DoSomething(); 
// end of inline CleanResource.doSomething() 

Au marqué emplacement (< ---), il n'y a aucune référence à l'instance CleanResource, et donc il est éligible pour la collecte et la méthode finalizer appelée. Comme le finaliseur peut être appelé à tout moment après l'effacement de la dernière référence, il est possible que le finaliseur et le reste du CleanResource.doSomething() s'exécutent en parallèle.

EDIT2: keepAlive() garantit que le pointeur this est accessible à la fin de la méthode, de sorte que le compilateur ne peut pas optimiser l'utilisation du pointeur. Et que cet accès est garanti à se produire dans l'ordre spécifié (le mot synchronisé marque une clôture qui interdit réordonnancement des lectures et écritures avant/après ce point.)

Original Post:

L'exemple dit que la méthode doSomething est appelée, et une fois appelée, les données référencées via le pointeur this peuvent être lues précocement (myIndex dans l'exemple).Une fois les données référencées lues, le pointeur this n'est plus nécessaire dans cette méthode, et le cpu/compilateur peut écraser les registres/déclarer que l'objet n'est plus accessible. Ainsi, le GC pourrait alors appeler simultanément le finaliseur en même temps que la méthode doSomething() de l'objet est en cours d'exécution.

Mais puisque le pointeur this n'est pas utilisé, il est difficile de voir comment cela aura un effet tangible.

EDIT: Eh bien, peut-être s'il y a des pointeurs mis en cache dans les champs de l'objet accessibles par cache, calculés this avant d'être récupéré, et l'objet est ensuite récupéré les références de la mémoire deviennent invalides. Il y a une partie de moi qui a du mal à croire que c'est possible, mais là encore, cela semble être un cas délicat, et je ne pense pas qu'il y ait quoi que ce soit dans JSR-133 pour empêcher cela par défaut. Il s'agit de savoir si un objet est considéré comme référencé uniquement par des pointeurs vers sa base ou par des pointeurs vers ses champs.

+0

Est-ce que cela pourrait également se produire sur une machine virtuelle basée sur une pile? Maintenant que je pense au niveau de bytecode comme vous êtes, je comprends mieux. Je suppose que dans le cas d'une machine de pile, la référence n'a pas besoin d'être sur la pile pour que 'invokevirtual' se termine? Donc, une fois que la valeur 'myIndex' est récupérée,' this' peut être retiré de la pile et potentiellement récupéré? –

+0

Sur une implémentation basée sur une pile stricte, cela n'est pas possible puisque le pointeur this restera sur la pile. Mais avec l'inlining de la méthode, l'exécution hors de l'ordre et l'allocation de registre, l'appel simultané au finaliseur devient possible. Voir ma dernière édition dans cette saga continue. :) – mdma

+0

Bonne réponse! Je ne savais même pas que quelqu'un d'autre que Dalvik VM d'Android avait implémenté une JVM basée sur le registre. Est-ce commun? –