7
votes

Dans quelle (s) situation (s) un point de référence à un objet qui était en file d'attente pour la collecte des ordures?

Je lis dans un sujet C # sur Dispose () et ~ finaliser et quand utiliser lequel. L'auteur affirme que vous ne devez pas utiliser de références dans votre ~ finaliser, car il est possible que l'objet que vous référencez peut déjà être collecté. L'exemple spécifiquement indiqué est, ".. Vous avez deux objets qui ont des références les unes aux autres. Si l'objet n ° 1 est collecté en premier, puis l'objet n ° 2 est en référence à un objet qui n'est plus là." / em>

Dans quels scénarios serait une instance d'un objet dans un état où elle a une référence en mémoire à un objet qui est en train d'être GC'D? Je suppose qu'il existe au moins deux scénarios différents, l'un où la référence d'objet pointe sur un objet et l'une dans laquelle la référence d'objet pointe à une autre référence d'objet (par exemple, lorsqu'elle a été transmise par REF dans une méthode).


4 commentaires

Le libellé exact est, ".. Vous avez deux objets qui ont des références les unes aux autres. Si l'objet n ° 1 est collecté en premier, puis l'objet n ° 2 est en référence à un objet qui n'est plus là." Ma question est, comment peut-on recueillir le n ° 1 si l'objet n ° 2 en a une référence?


Les références circulaires sont très courantes dans le code .NET, les gestionnaires d'événements les créent. Le GC n'a aucun problème avec eux. Le seul problème avec le finaliseur est que les objets ne sont pas finalisés dans un ordre particulier. Pas un problème non plus.


Mis à jour la question - espérons-le plus clairement sur ce que je cherche.


@HENKHOLTERMAN C'est la tête première C # par O'Reilly. Notez que ce ne serait pas la première erreur technique que j'ai rencontrée, mais je suppose que c'est à attendre de la plupart des livres comme celui-ci.


6 Réponses :


12
votes

Vous pouvez avoir des objets qui se référennent mutuellement, et l'ensemble de l'ensemble peut être éligible pour GC.


Voici un simple code d'échantillon: P>

class Test { 
     public Test Other { get; set;} 

     static void Main()
     {
          Test one = new Test();
          Test two = new Test { Other = one; }
          one.Other = two;

          one = null;
          two = null
          // Both one and two still reference each other, but are now eligible for GC
     }
}


12 commentaires

Maintenant que j'ai lu cette réponse, je pense avoir peut-être mal compris la question.


Bien que cette déclaration puisse être vraie, malheureusement, cela n'aide pas, car il n'explique toujours pas pourquoi ils peuvent être (ou sont) éligibles


@chopperDave ce qui n'est pas clair? Le GC n'utilise pas de comptage de référence - dès que l'ensemble des objets interconnectés n'est pas enraciné, ils sont éligibles, mais ils peuvent toujours se réferner. Prenez 2 objets, référencé par des variables "A" et "B", où les deux objets se rapportent les uns aux autres. Définissez les deux variables à NULL. Ils se réfèrent toujours mutuellement, mais sont maintenant éligibles pour GC.


@CHOPPERDAVE: Imaginez un système de fichiers, où vous avez des dossiers contenant d'autres fichiers et dossiers. Lorsque vous supprimez un dossier, tous les dossiers et les fichiers à l'intérieur sont supprimés immédiatement. Avec des systèmes de fichiers, vous obtenez rarement à observer un état où vous avez une poignée ouverte dans un fichier, mais son dossier parent n'existe plus (bien qu'il soit plus facile de le voir sur Unix que Windows), mais dans le cas où les "fichiers" sont des objets. Dans votre programme, un finaliseur vous permet de "se faufiler" et vous pouvez rencontrer un état où les frères et les parents sont des "fantômes" qui n'existent pas vraiment.


@Reedcopsey avant les commentaires de ce message, je n'étais pas au courant des concepts d'une «racine» ou de «comptage de référence». Étant donné que vous ne semblez toujours pas répondre à la question que j'essaie de demander, je suppose que je ne pose pas la question correctement. Laissez-moi essayer de clarifier.


@DanielPryden comprend complètement ce concept mais cela ne s'applique pas à la question que j'essaie d'avoir répondu à ce stade - par mon commentaire juste au-dessus de celui-ci, peut-être que je ne pose peut-être pas la question de la bonne manière.


@chopperdave Je pense que vous avez un malentendu fondamental ici. De votre commentaire "GC exige toujours qu'un objet soit fabriqué inaccessible (pas de références) avant de ne rien faire" - le GC nécessite qu'il n'y ait pas de "racines" à l'objet de votre code , pas qu'il n'y a pas de références. La seule exigence est qu'il n'y a pas de références à cet objet utilisé par votre processus, mais il existe très souvent de nombreuses références à l'objet. Les références de votre code sont les "racines" à l'objet - donc une fois que la racine est partie (c.-à-d .: la variable dépasse ou est définie sur NULL)


@Reedcopsey ahh. Ok je pense que je sais ce que vous obtenez, mais ce n'est toujours pas clair à 100%. Je ne suppose pas que vous puissiez préparer un selippet de code rapide qui démontre cette situation ou peut-il me diriger l'un?


... Ensuite, l'objet devient admissible à GC, que d'autres objets "non manifestés" ont toujours une référence à cet objet spécifique. Un "réseau" d'objets d'auto-référencement interdépendants peut devenir admissible au GC à une période de temps.


@chopperdave a édité mon message à la démonstration.


@Reedcopsey a mis à jour ma question.


Laissez-nous Continuer cette discussion en chat



0
votes

Il y a un terme appelé résurrection in .NET.

En bref, la résurrection peut se produire lorsque votre objet est dans la file d'attente de finalisation, mais lorsque le finaliseur ( ~ ~ classname () méthode) est appelé, il renvoie l'objet à la partie. Par exemple: xxx

Vous pouvez en savoir plus ici: Résurrection d'objet à l'aide de GC.ReReGisterisfinalize . Mais je recommanderais vraiment le livre CLR via C # Par Jeffrey Richter comme il a expliqué ce sujet en profondeur dans l'un des chapitres.


5 commentaires

Intéressant, mais comment l'objet que j'ai référencé est-il entré dans la file d'attente de finalisation s'il n'est jamais devenu inaccessible?


Oui, désolé pour ça. C'était vraiment accessible. Les objets qui ont des méthodes de finaliseur sont dans la file d'attente de finalisation par défaut et seulement lorsqu'ils ne sont plus accessibles à partir des racines de l'application sont-ils placés dans le F-accessible. Cela se fait car le système doit toujours avoir des références aux objets afin d'appeler leurs finaliseurs.


@HENKHOLTERMAN: Comment Microsoft a-t-il proposé ces noms? Ils semblent en arrière pour moi. L'ensemble d'objets avec des finaliseurs enregistrés ressemble plus à un sac qu'une file d'attente, je pense donc que le terme "file d'attente" serait plus applicable à la liste des objets qui doivent faire fonctionner leurs finaliseurs dès que possible. Il semble également étrange que le nom "frishable" soit utilisé pour une liste d'objets figurant sur la liste parce qu'ils étaient un accessible. J'aimerais vous référer à ces listes en utilisant leurs noms réels, sauf que les noms sont suffisamment arriérés, semblant simplement ajouter à la confusion.


@HENKHOLTERMAN: Cela fait, mais le fait que les objets de cette liste soient accessibles n'est pas ce qui est important à leur sujet. Tout objet pourrait être placé dans cette liste et décrit comme "accessible". Si j'avais mes druthers, les structures de données seraient la "liste de finaliseurs enregistrés" et la "file d'attente de finalisation". BTW, je me demande si quelque chose est vraiment gagné par tous les objets incluent une méthode finaliser , par opposition à une classe distincte finalisfableObject classe de base. Si une classe n'est pas configurée pour gérer les ressources non gérées, les classes dérivées ne devraient généralement pas avoir. Au lieu de cela, ils devraient ...


... encapsuler toutes les ressources non gérées dans des classes de ressources gérées, puis organiser des références à celles-ci. Il semble curieux que tous les dérivés d'objet reçoivent un emplacement à visser pour finaliser () quand il n'est pertinent que pour une très petite minorité d'entre elles.



2
votes

En bref, des objets qui ne sont pas accessibles à partir d'une racine GC (champ statique, paramètre de méthode, variable locale, variable enregistrée) en suivant la chaîne de références sont éligibles à la collecte des ordures. Donc, il est pleinement possible que, par exemple, l'objet A se réfère à B qui fait référence à C qui fait référence à D, mais soudainement une nulls sur sa référence à B, auquel cas B, C et D peut tous être collectés.


2 commentaires

C'est très proche de ce que je cherche, je pense. Pour reformuler ce que vous dites, si ma référence à un objet est en réalité une référence à une référence (car elle m'a été transmise via une méthode), alors il est possible de reporter que mes références d'objet sont supprimées sans que je connaisse et ainsi Lorsque j'essaie de l'utiliser, j'obtiens une exception de référence nulle. Est-ce correct? Qu'en est-il dans le cas où la référence de mon objet est une référence directe à un objet?


@CHOPPERDAVE: Un programme .NET inclut des métadonnées substantielles, de sorte qu'à tout moment donné dans l'exécution d'un programme, le collecteur des ordures pourra identifier toutes les références d'objet que le code en direct pourrait éventuellement accéder. Si le troisième paramètre sur votre méthode était une référence d'objet, le GC est déclenché lorsque votre méthode est en cours d'exécution et que votre code n'a pas atteint un point où il ne recherchera clairement plus jamais ce troisième paramètre, le GC ne permettra pas à l'objet. mentionné par ce paramètre à collecter.



4
votes

Normalement, le GC ne récupérera que la mémoire pour les objets qui n'ont aucune référence qui leur indique. Cependant, des objets avec finaliseurs sont traités différemment.

Voici ce que msdn dit à ce sujet :

Récupération de la mémoire utilisée par les objets avec les méthodes de finalisation nécessite au moins deux collections de déchets. Quand le collecteur des ordures fonctionne une collection, il récupère la mémoire pour des objets inaccessibles sans finaliseurs. A ce moment-là, il ne peut pas collecter les objets inaccessibles qui ont des finaliseurs. Au lieu de cela, il supprime les entrées pour ces objets de la file d'attente de finalisation et les place dans une liste de objets marqués comme prêt pour la finalisation. [...]
Le collecteur des ordures appelle les méthodes finaliser pour les objets de cette liste, puis supprime les entrées de la liste. Une future collection de déchets déterminera que les objets finalisés sont vraiment des ordures car elles ne sont plus signalées par des entrées dans la liste des objets marqués comme prêt à la finalisation.

donc il y a aucune garantie que d'autres objets référencés dans un finaliseur seront toujours utilisables lorsque le finaliser la méthode est exécuté par le GC, car ils ont peut-être déjà été finalisés lors d'une collection de déchets antérieure tandis que le L'objet lui-même attendait d'être finalisé .


3 commentaires

Si je le lisez correctement, GC exige toujours qu'un objet soit fabriqué inaccessible (pas de références) avant de ne rien faire, que cela ait une méthode de finalisation ou non. Revenir à ma question, comment un objet qui est référencé par un autre objet (et donc accessible assumant) soit-il de GC'D?


+1: meilleure réponse jusqu'à présent parce que vous avez expliqué pourquoi vous ne devriez pas essayer d'accéder à un autre objet dans le finaliseur.


Il ne sert à rien de garantir que les objets seront dans un état que toutes les méthodes fournies par l'utilisateur particulier "comme", mais la seule chose que le GC permettra de "spontanément" arriver à n'importe quel objet lorsqu'il soit accessible via n'importe quel moyen pour objet.finalize () doit être appelé. Le collecteur des ordures ne fera rien pour perturber le contenu des champs d'objets autres que provoquer l'exécution de objet.finalize . objet.finalize () peut, bien sûr, faire ce qu'il veut des champs d'objets.



1
votes

"... puis l'objet n ° 2 La référence est de pointe vers un objet qui n'est plus là."

qui va jamais arriver. Chaque fois que votre code a accès à une référence, l'objet en cours de référence existe toujours. Ceci s'appelle la sécurité de la mémoire et cela reste toujours lorsqu'il est en cours de finalisation en arrière-plan. Une référence jamais pointe vers une instance collectée.

mais un objet existant non collecté aurait déjà pu être finalisé (disposé). C'est probablement ce que votre avertissement fait référence à. xxx


4 commentaires

"Une référence jamais pointe vers une instance collectée." - une liste doublement liée qui a de nombreux nœuds est sur le point d'être collectés. Dans la liste pour chaque paire de nœuds séquentiellement, le nœud A a une référence au nœud B et node B a une référence au nœud A. Quelqu'un doit être collecté en premier.


@Thatchuck - "Quelqu'un doit être recueilli d'abord" qui ne sera pas un comportement observable, c'est-à-dire pas aussi loin que votre code peut le dire. Et dans Gen-0 au moins, tout le monde est collecté dans la même tick d'horloge exacte.


@Thatatchkguy: Pour qu'une combinaison de bits soit une référence à autre chose, il doit y avoir quelque chose quelque part quelque part sur cette manière que la combinaison de bits doit être interprétée comme une référence, plutôt que comme un entier ou un ou plusieurs personnages, ou un programme instruction, ou autre chose. Lorsque GC est en cours d'exécution, les objets anciens et nouveaux vivront dans des zones de mémoire marquées comme contenant des objets, de sorte que toutes les références dans les domaines de la mémoire puissent être considérées comme des «références réelles» (par opposition à autre chose) . Une fois que le GC est fait, cependant, ...


... Le système abandonnera tout ce qui n'a pas été marqué pour être gardé, ce qui signifie que rien ne reviendra plus jamais ces zones de mémoire jusqu'à ce qu'elles n'aient pas été écrasées avec de nouvelles données. Bien qu'il puisse y avoir des cas d'angle étranges impliquant des choses telles que affaiblies types, à toutes fins utiles, la mémoire utilisée par tous les objets inutilisés dans une "génération" de GC donnée sera disponible simultanément à la réutilisation.



1
votes

Malheureusement, il y a beaucoup d'utilisation négligée de la terminologie autour de la collecte des ordures, ce qui provoque une grande confusion. Un "éliminateur" ou "finaliseur" ne détruit pas réellement un objet, mais sert plutôt à retarder la destruction d'un objet qui serait sinon être admissible à la destruction, jusqu'à ce que après elle a eu une chance de mettre ses affaires en ordre (c'est-à-dire généralement en laissant d'autres choses savoir que leurs services ne sont plus nécessaires).

Il est plus simple de penser au collecteur des ordures "Arrêter le monde", comme étant les étapes suivantes, dans l'ordre:

  1. à tous les articles qui sont suffisamment nouveaux qu'ils pourraient être considérés comme des "ordures".
  2. Visitez chaque racine de collecte des ordures (c.-à-d. chose qui est intrinsèquement "en direct"), et s'il n'a pas encore été copié, copiez-le sur un nouveau tas, mettez-le à jour la référence au point sur le nouvel objet et et visitez. Tous les articles auxquels il contient des références (qui les copieront s'ils n'ont pas été copiés). Si on visit un article dans l'ancien tas qui avait été copié, il suffit de mettre à jour la référence utilisée pour la visiter.
  3. Examinez chaque article qui s'est inscrit à la finalisation. S'il n'a pas encore été copié, désenregistrez-le pour la finalisation, mais annoncez une référence sur une liste d'objets qui doivent être finalisés le plus rapidement possible.
  4. Les éléments de la liste de finalisation immédiate sont considérés comme "vivants", mais comme ils n'ont pas encore été copiés, visitez tous les articles de cette liste et, si ce n'est pas encore copié, copiez-le sur le nouveau tas et visitez tous les articles à laquelle il tient des références.
  5. abandonner l'ancien tas, car personne ne fera des références à rien dessus.

    Il est intéressant de noter que, tandis que certains autres systèmes de collecte des ordures fonctionnent en utilisant des pointeurs à double indirecte pour références, le collecteur de déchets .NET (au moins l'arrêt normal "l'arrêt du monde") utilise des pointeurs directs. Cela augmente quelque peu la quantité de travail que le collectionneur doit faire, mais elle améliore l'efficacité du code qui manipule des objets. Étant donné que la plupart des programmes passent plus de leur temps à manipuler des objets qu'ils passent à collecter des ordures, il s'agit d'une victoire nette.


0 commentaires