Collecte des ordures et Finalisateurs: des points plus fins

en répondant à une autre question* sur SO, et la discussion de commentaire qui a suivi, je suis tombé dans un mur sur un point que je ne suis pas clair sur.

corrigez-moi sur tout point où je suis égaré...

quand le collecteur de déchets recueille un objet, il appelle le finalizer de cet objet, sur un fil séparé (à moins que le finalizer ait été supprimé, par exemple par un Dispose() méthode). Lors de la collecte, le GC suspend tous les threads sauf le thread qui a déclenché la collecte (à part la collection de fond).

ce qui n'est pas clair:

  1. le ramasseur d'ordures attend-il que le finaliseur exécute sur cet objet avant de le ramasser?
  2. dans le cas contraire, est-ce que cela enlève les threads pendant que le finalizer est encore en cours d'exécution?
  3. si elle attend, que se passe-t-il si le finaliseur se heurte à une serrure retenue par l'un des fils suspendus? Le finaliseur blocage de thread? (Dans ma réponse, je soutiens que c'est une mauvaise conception, mais Je pourrais peut-être voir des cas où cela pourrait se produire)

* lien vers la question originale:

.NET GC accédant à un objet synchronisé à partir d'un finaliseur

12
demandé sur Wai Ha Lee 2011-03-07 21:00:05

3 réponses

Le collecteur d'ordures attend-il que le finaliseur exécute sur cet objet avant de le ramasser?

Votre question est un peu ambigu.

lorsque le GC rencontre un objet" mort " qui doit être finalisé, il abandonne sa tentative de récupérer le stockage de l'objet mort. Au lieu de cela, il met l'objet dans une file d'attente de "les objets que je sais besoin de finalisation" et traite cet objet comme vivant jusqu'à ce que le thread finalizer soit fait avec il.

donc, oui, le GC "attend" jusqu'à ce que le finalizer soit exécuté avant de récupérer le stockage. Mais il ne fait pas attendre synchrone. On dirait que vous vous demandez "est-ce que le GC appelle le finaliseur de façon synchrone juste là?"Non, il met l'objet à être finalisé plus tard et garde truckin'. Le GC veut passer rapidement à travers la tâche de libérer les ordures et de compacter la mémoire de sorte que le programme proprement dit puisse reprendre l'exécution ASAP. Il ne va pas arrêter pour faire face à un objet pleurnichard qui demande de l'attention avant qu'il ne soit nettoyé. Il met l'objet dans une file d'attente et dit "calme et le finaliseur thread d'accord avec vous plus tard."

plus tard, le GC vérifiera à nouveau l'objet et dira "êtes-vous toujours mort? Et a votre finaliseur exécuter?"Si la réponse est "oui", alors l'objet sera remis en état. (Rappelez-vous, un finalisateur pourrait transformer un objet mort en un objet vivant; essayez de ne jamais le faire. Rien de plaisant n'arrive résultat.)

est-ce qu'il désactive les threads pendant que le finalizer est encore en cours d'exécution?

je crois que le GC dégèle les fils qu'il a gelés, et signale le fil finalisateur "Hé, vous avez du travail à faire". Ainsi, lorsque le thread finalizer commence à fonctionner, les threads qui ont été gelés par le GC redémarrent.

il pourrait y avoir des threads non gelés parce que le finalizer pourrait nécessiter un appel pour être marshalled à un thread utilisateur dans ordre de libérer une ressource fil-affinitisée. Bien sûr, certains de ces threads utilisateur être bloqué; les fils peuvent toujours être bloqué par quelque chose.

que se passe-t-il si le finaliseur se heurte à une serrure retenue par l'un des fils suspendus? Le finaliseur blocage de thread?

Vous betcha. Il n'y a rien de magique dans le thread finalizer qui l'empêche de se bloquer. Si un thread utilisateur est en attente sur une serrure retirée par le thread finalizer, et le thread finalizer attend sur un verrou retiré par le thread utilisateur, alors vous avez une impasse.

Les exemples de blocages de finalizer thread abondent. Voici un bon article sur un tel scénario, avec un tas de liens vers d'autres scénarios:

http://blogs.microsoft.co.il/blogs/sasha/archive/2010/06/30/sta-objects-and-the-finalizer-thread-tale-of-a-deadlock.aspx

comme le dit l'article:finaliseurs sont extrêmement complexe et dangereux mécanisme de nettoyage et vous devriez éviter si vous pouvez éventuellement. Il est incroyablement facile d'obtenir un finaliseur mal et très difficile à obtenir ce droit.

45
répondu Eric Lippert 2011-03-07 19:57:11

les objets qui contiennent un finalizer ont tendance à vivre plus longtemps. Quand, lors d'une collecte, le GC marque un objet avec un finaliseur comme étant des déchets, il ne recueille pas cet objet (encore). Le GC ajoutera cet objet à la file d'attente finalizer qui exécutera après le GC est terminé. Conséquence de cela est que, parce que cet objet n'est pas collecté, il se déplace à la prochaine génération (et avec ça, tous les objets qu'il désigne).

le GC suspend tous les threads en cours d'exécution. Le finaliseur thread peut s'exécuter en arrière-plan pendant que l'application continue de fonctionner. Le finalizer appelle toutes les méthodes de finalisation sur tous les objets qui sont enregistrés pour finalisation. Après que la méthode finalizer sur un objet a couru, l'objet sera retiré de la file d'attente, et à partir de ce point sur l'objet (et peut-être tous les objets auxquels il renvoie encore) est poubelle. La prochaine collecte qui nettoie les objets de la génération de cet objet sera (enfin) à supprimer cet objet. Depuis les objets qui vivent dans la génération 2 sont recueillis environ 10 fois moins que les objets qui vivent dans la génération 1, et gen 1 dix fois moins que gen 0, Il peut prendre un certain temps pour un tel objet est enfin ordures collectées.

parce que le thread finalizer est juste un thread simple qui exécute le code géré (il appelle les finaliseurs), il peut bloquer et même verrouiller. Pour cette raison, il est important de faire le moins possible pour finaliser les méthodes. Parce que le finalizer est un fil d'arrière - plan, un faute de méthode de finalisation pourrait même faire tomber L'AppDomain complet (Beurk!).

on pourrait dire que cette conception est malencontreuse, mais si vous y pensez, d'autres conceptions où le cadre nettoie notre désordre efficacement, sont difficiles à imaginer.

alors, pour répondre à vos questions:

  1. Oui, seulement après que l'objet a été retiré de la file d'attente finalizer, l'objet sera un déchet et le GC le ramassera.
  2. le GC suspend tous les fils, même le finaliseur file d'attente.
  3. la file d'attente du finaliseur peut être bloquée. Verrouiller le moins possible à l'intérieur des méthodes de mise au point.
4
répondu Steven 2011-03-16 08:48:01

il est plus simple de penser que le ramasseur d'ordures divise les objets en quatre groupes:

  1. ceux qui ne sont accessibles par aucun objet enraciné;
  2. ceux qui sont accessibles à partir d'une liste d'objets finalisables vivants, mais pas à partir d'autres objets enracinés;
  3. ceux qui sont sur la liste des objets finalisables en direct, mais qui sont également accessibles à travers un autre objet enraciné que cette liste.
  4. Ceux qui ne sont pas sur la liste de live finalisable objets, mais sont accessibles par le biais de certains enracinée objet autre que celui de la liste.

lorsque le collecteur d'ordures fonctionne, les objets de type #1 disparaissent. Les objets de la catégorie #2 sont ajoutés à une liste d'objets nécessitant une finalisation imminente et retirés de la liste "live finalisable objects" (devenant ainsi des objets de la catégorie #4). Notez que la liste des objets devant être finalisés est une référence normale enracinée, donc les objets de cette liste ne peuvent pas être collectés tant qu'ils y sont, mais si non autre référence enracinée est créée au moment où le finalizer est terminé l'objet va passer à la catégorie #1.

3
répondu supercat 2015-04-27 15:11:59