GC.Collect() et Finaliser

Ok, on sait que GC appelle implicitement des méthodes Finalize sur les objets lorsqu'il identifie cet objet comme étant une poubelle. Mais que se passe-t-il si je fais un GC.Collect()? Les finaliseurs sont-ils toujours exécutés? Une question stupide peut-être, mais quelqu'un m'a posé ceci et j'ai répondu "oui" et puis j'ai pensé: "était-ce tout à fait correct?"

45
demandé sur Ian R. O'Brien 2012-12-19 18:46:52

5 réponses

Ok, on sait que GC appelle implicitement les méthodes Finalize sur les objets lorsqu'il identifie cet objet comme étant une poubelle.

Non non Non. Ce n'est pas connu parce que, pour être savoir, une déclaration doit être true. Cette déclaration est false. le garbage collector n'exécute pas les finaliseurs car il trace , qu'il s'exécute lui-même ou que vous appeliez Collect. le thread finalizer exécute les finaliseurs après le collecteur de traçage a trouvé la poubelle et cela se produit de manière asynchrone par rapport à un appel à Collect. (Si cela arrive du tout, ce qui pourrait ne pas être le cas, comme le souligne une autre réponse.) C'est-à-dire que vous ne pouvez pas compter sur le thread finalizer s'exécutant avant que le contrôle ne retourne à partir de Collect.

Voici un croquis trop simplifié de la façon dont cela fonctionne:

  • Lorsqu'une collection se produit, le thread de traçage du garbage collector trace les racines - les objets connus pour être vivants, et tous les objets auxquels ils se réfèrent à, et ainsi de suite-pour déterminer les objets morts.
  • les objets "morts" qui ont des finaliseurs en attente sont déplacés dans la file d'attente des finaliseurs. la file d'attente du finaliseur est une racine . Par conséquent, ces objets "morts" sont en fait encore vivants .
  • le thread finalizer, qui est généralement un thread différent du thread de traçage GC, finit par s'exécuter et vide la file d'attente finalizer. Ces objets deviennent alors vraiment morts et sont collectés dans la collection suivante sur le fil de traçage. (Bien sûr, puisqu'ils viennent de survivre à la première collection, ils pourraient être dans une génération supérieure.)

Comme je l'ai dit, c'est trop simplifié; les détails exacts du fonctionnement de la file d'attente du finaliseur sont un peu plus compliqués que cela. Mais il obtient assez de l'idée à travers. Le résultat pratique ici est que Vous ne pouvez pas supposer que l'appel Collect exécute également les finaliseurs, car ce n'est pas le cas. Permettez-moi de répéter cela une fois de plus: la partie de traçage du garbage collector fait Pas exécuter les finaliseurs , et Collect exécute uniquement la partie de suivi du mécanisme de collecte.

Appelez le bien nommé WaitForPendingFinalizers Après avoir appelé Collect si vous voulez garantir que tous les finaliseurs ont été exécutés. Cela mettra en pause le thread actuel jusqu'à ce que le thread finalizer se déplace pour vider la file d'attente. Et si vous voulez vous assurer que ces objets finalisés ont leur mémoire récupérée, vous devrez appeler Collect a second temps.

Et bien sûr, il va sans dire que vous ne devriez le faire qu'à des fins de débogage et de test. Ne faites jamais ce non-sens dans le code de production sans vraiment, vraiment bonne raison.

74
répondu Eric Lippert 2012-12-19 15:39:58

En fait la réponse "ça dépend". En fait, il existe un thread dédié qui exécute tous les finaliseurs. Cela signifie que l'appel à GC.Collect n'a déclenché que ce processus et que l'exécution de tous les finaliseurs serait appelée de manière asynchrone.

Si vous voulez attendre que tous les finaliseurs soient appelés, vous pouvez utiliser l'astuce suivante:

GC.Collect();
// Waiting till finilizer thread will call all finalizers
GC.WaitForPendingFinalizers();
15
répondu Sergey Teplyakov 2012-12-19 14:56:25

Oui, mais pas tout de suite. Cet extrait est de Garbage Collection: gestion automatique de la mémoire dans le Microsoft. NET Framework (MSDN Magazine) (*)

" Lorsqu'une application crée un nouvel objet, le nouvel opérateur alloue la mémoire dans le tas. Si le type de l'objet contient un Finaliser méthode, puis un pointeur vers l'objet est placé sur la finalisation file. La file d'attente de finalisation est une structure de données interne contrôlée par le garbage collector. Chacun l'entrée dans la file d'attente pointe vers un objet cela devrait avoir sa méthode Finalize appelée avant la mémoire de l'objet peut être récupérée.

Quand un GC se produit ... le garbage collector analyse la finalisation file d'attente à la recherche de pointeurs vers ces objets. Quand un pointeur est trouvé, le pointeur est supprimé de la file d'attente de finalisation file d'attente freachable (prononcé "F-accessible"). La file d'attente freachable est une autre structure de données interne contrôlée par la poubelle collecteur. Chaque pointeur dans la file d'attente freachable identifie un objet qui est prêt à avoir sa méthode de finalisation appelée.

Il existe un thread d'exécution spécial dédié à L'appel de Finalize méthode. Lorsque la file d'attente freachable est vide (ce qui est cas), ce fil dort. Mais lorsque les entrées apparaissent, ce fil se réveille, supprime chaque entrée de la file d'attente et appelle Finalize de chaque objet méthode. Pour cette raison, vous ne devez pas exécuter de code dans un Finalize méthode Cela fait toute hypothèse sur le thread qui exécute le code. Par exemple, évitez d'accéder au stockage local du thread dans le Méthode Finalize."

( * ) depuis novembre 2000, les choses ont peut-être changé depuis.

10
répondu stuartd 2012-12-19 14:55:22

Lorsque les déchets sont collectés (que ce soit en réponse à la pression de la mémoire ou GC.Collect()), les objets nécessitant une finalisation sont mis en file d'attente de finalisation.

Sauf si vous appelez GC.WaitForPendingFinalizers(), les finaliseurs peuvent continuer à s'exécuter en arrière-plan longtemps après la fin de la collecte des ordures.


BTW, il n'y a aucune garantie que les finaliseurs seront appelés du tout . De MSDN ...

La méthode Finalize peut ne pas s'exécuter jusqu'à la fin ou ne pas s'exécuter à dans tous les les circonstances exceptionnelles suivantes:

  • un autre finaliseur bloque indéfiniment (entre dans une boucle infinie, essaie d'obtenir un verrou qu'il ne peut jamais obtenir et ainsi de suite). Parce que le runtime tente d'exécuter les finaliseurs à la fin, d'autres finaliseurs pourrait ne pas être appelé si un finaliseur bloque indéfiniment.
  • le processus se termine sans donner à l'exécution une chance de nettoyer. Dans ce cas, la première notification du processus du runtime la résiliation est un Dll_process_detach notification.

L'exécution continue de finaliser les objets pendant l'arrêt uniquement le nombre d'objets finalisables continue de diminuer.

5
répondu Branko Dimitrijevic 2012-12-19 15:14:30

Quelques points supplémentaires valent la peine d'être mentionnés ici.

Finalizer est le dernier point où les objets. net peuvent libérer des ressources non gérées. Les finaliseurs ne doivent être exécutés que si vous ne disposez pas correctement de vos instances. Idéalement, les finaliseurs ne devraient jamais être exécutés dans de nombreux cas. parce que l'implémentation de dispose appropriée devrait supprimer la finalisation .

Voici un exemple d'implémentation IDispoable correcte .

Si vous appelez le dispose méthode de tous les objets jetables, il devrait effacer toutes les références et supprimer la finalisation. S'il y a un développeur pas si bon qui oublie d'appeler la méthode Dispose, Finalizer est l'économiseur de vie.

0
répondu CharithJ 2017-05-23 12:17:43