9
votes

Est-ce que GC libère la mémoire utilisée

J'ai un objet parent qui a une référence à un objet enfant et que le parent a également un gestionnaire d'événements qui écoute un événement de l'objet enfant.

Si toutes les références à l'objet parent seront publiées , la mémoire utilisée à travers le parent et l'enfant sera-t-elle libérée par GC? STRUT> (en supposant que plus aucune référence ni à l'enfant ni au parent n'existe). P>

class ParentClass {

    ChildClass _childClass;

    public ParentClass(ChildClass childClass) {
        _childClass = childClass;
        childClass.SomeEvent += ChildClass_SomeEvent;
    }

    void ChildClass_SomeEvent(object sender, SomeEventArgs e) {

    }
}


0 commentaires

4 Réponses :


9
votes

Oui. .NET GC gère des références circulaires sans problèmes (en supposant que vous n'utilisez pas de ressources non gérées ou que vous implémentez Idisposable si vous le faites).


7 commentaires

Le GC peut gérer toute situation de référencement comme vous le dites, mais je pense que vous devez clarifier l'utilisation de l'enregistrement de l'événement dans l'exemple de l'OPS - que se passe-t-il dans ce scénario? Tous les gestionnaires d'événements laissés enregistrés sont des sources de fuites de mémoire potentielles dans .NET (une fuite de mémoire signifiant GC ne peut pas collecter).


C'est une question que j'ai depuis longtemps. Ce n'est pas spécifique pour les événements, je choisis les événements comme exemple, car mon anglais est si mauvais que je n'avais pas la possibilité d'expliquer les vraies questions afin que quelqu'un comprenne et envoie une réponse. C'est sur les références circulaires de deux objecjets qui sont par rapport aux autres mais n'ont aucune autre référence. Et la discussion est entrée exactement sur ce sujet.


Je dirais ensuite de modifier votre op pour supprimer l'utilisation de l'événement - comme les événements introduisent une couche de complexité en haut de votre question qui ne peut être sans doute laissée non modifiée. De toute façon, bonne question :-)


> "Vous implémentez Idisposable" ... et appelez manuellement la méthode Dispose ().


@ Mishal153: Si GC ne résout pas la référence circulaire, il est clair que je dois faire quelque chose (comme je l'ai déjà fait beaucoup de fois dans de nombreux projets). Mais la quesion est, si elle est même nécessaire de le faire dans un tel cas. Implémentation Idisposable Je n'aime pas pour une telle chose, car elle dirige l'utilisateur de la classe dans une mauvaise direction. IMO Idisposable est principalement utilisé, si des ressources non gérées sont utilisées. Mais probablement c'est un détail.


@HCL - GC fait résoudre les références circulaires. Si A a une référence à B et B a une référence à A , mais il n'y a pas d'autres références à a ou b , ils seront tous deux collectés.


Le problème des événements est imho que de nombreux développeurs ne connaissent pas (ou ignorent) le fait que les événements créent une référence forte de la source à l'auditeur. Si vous le savez, vous pouvez mettre en œuvre des contre-mesures (comme des modèles d'événements faibles, vous désabonner, ...).



3
votes

Eh bien, si rien d'autre n'utilisait la même instance de _childclass , oui, il serait collecté. Cependant, je ne suis pas sûr de quoi les implications ne vous donnent pas de détachement du gestionnaire d'événements - les gestionnaires d'événements laissés enregistrés sont une source de fuites de mémoire ONU-GC-ACIPABLE dans les applications .NET - la vôtre est une question intéressante sur le problème.

Mise à jour: s'avère que ma compréhension de la source de la fuite de mémoire était à l'envers. Si la classe A abonna à la classe B mais ne se désabonne pas, une volonté ne sera collectée que lorsque B est collectée (car la liste d'abonnement de B est en vie). Pour les sources d'événements de longue durée, cela peut être un problème pour les abonnés.

Le problème se produit dans tous les espaces de vie d'objet, mais en réalité, si B est tout aussi de courte durée que A, la fuite n'est pas remarquée et non un problème.

update 2: Dans le cas de l'exemple de l'OP, l'enfant est la source et le parent est l'abonné - le GC peut gérer cette situation - tant que l'enfant n'est pas référencé en dehors du parent, ce qui signifie que l'enfant n'est pas éligible pour la collecte (cela gardera le parent vivant).


20 commentaires

Les gestionnaires d'événements et les délégués d'événements IMHO en général ne sont pas différents de toutes les autres références. Ainsi, aucun détachement explicite n'est nécessaire. Les manipulateurs d'événements sont une source infâme de «fuites de mémoire» parce que les gens ont tendance à leur oublier, mais c'est à ce sujet.


Si vous enregistrez un événement de la classe A en classe B, puis A est recueilli, vous ne pourrez pas collecter b à cause de la référence décédée du gestionnaire d'événements enregistré par classe A - c'est la source de fuites dans .NET .NET . Si un supprime l'abonnement dans le finaliseur ou autrement avant la collecte, alors B peut être collecté normalement aussi.


UUPS, si je comprends bien, des références circulaires ne sont pas résolues?


Les délégués d'événements contiennent faibles références autant que je sache, et n'empêchez donc pas une classe étant des ordures collectées. Cela peut être une source de fuites de mémoire dans d'autres environnements, mais pas que j'ai rencontré.


Attendez ... Dans votre exemple, quelle classe (A ou B) a l'événement et lequel l'abonne? Ne comprend pas bien votre phrase ... Quoi qu'il en soit, je pense que vous avez mal compris quelque chose. Supprimer les gestionnaires d'événements dans un finisseur pour permettre la collecte des ordures va complètement contre chaque article sur l'idéologie .NET GC que j'ai jamais lu. Les finaliseurs sont destinés à nettoyer les ressources non gérées et non gérées. En fait, essayer d'écrire un finisseur est surtout une mauvaise idée même. Ici, lisez cet article: Bluebytesoftware.com/ Blog / ...


Oups, semble avoir tort à ce sujet - de telles références aux manipulateurs d'événements sont fortes. Il y a eu beaucoup de discussions sur eux étant faible cependant, et je suis certainement un défenseur de cela.


@Noldorin: Je n'ai jamais creusé très profondément dans ces choses, mais dans la mesure où je me souviens de faibles références, c'est un concept plus récent et non réalisé avec les événements CLR. Ai-je tort?


@Noldorin et @ Vilx Je n'essaie pas d'être argumentatif, mais Google "C # Fuite de la mémoire du gestionnaire d'événements" et vous trouverez des exemples. Je ne me souviens plus d'exemples spécifiques du sommet de ma tête, l'exemple que j'ai donné est ce que je est le cas. Si je me trompe, je reste corrigé :-)


@Adam: Non, c'est assez juste. :) Tu as manqué mon dernier commentaire, bien que je pense heh. Il y a eu tellement de discussions à ce sujet, dont certaines dans lesquelles je lisais un moment, je me suis heurté à la vérité de l'État actuel. @HCL: Les références faibles sont déployées depuis que le CLR a été conçu aussi loin que je sache. Mais oui, pas utilisé avec des événements / délégués.


Comme je le vois: Supposons qu'un objet a a un événement E . Supposons également un objet B a une méthode m . Maintenant, si a.e + = b.m; alors: (1): si toutes les références à a sont perdues, c'est GC'ed. B est pas Tenir une référence à A . (2): si toutes les références à B sont perdues (à l'exception de l'événement), il est pas GC'ed, car A a toujours une référence à B . (3): Si toutes les références à ces deux sont perdues, elles sont toutes deux gc'ed. En d'autres termes, ce n'est pas différent de si a avait un champ p qui serait défini sur b .


@Vilx Vous avez d'utiliser le finaliseur si vous ne pouvez pas appeler Disposer (c'est-à-dire que vous ne savez pas lorsque votre objet est éliminé car vous écrivez un complément à une autre application). Oui, c'est pour des trucs non gérés, mais aussi pour le nettoyage de la dernière chance à la berline pour éviter les fuites de mémoire, quelque chose que les manutentionnaires d'événements peuvent introduire. Mon exemple, B a l'événement, un événement abonné à B, A est annulé la liste d'invocation de B. B. La liste d'invocation de B a-t-elle un inscrit sans source. Vous avez raison que si vous, disons, avait une connexion SQL non fermée, vous ne la fermeriez toujours pas du finaliseur - mais ceci est uniquement parce que la connexion sera GC'D.


@Vilx non, je crois que c'est l'inverse. La classe avec l'événement ne peut pas être disposée car la liste d'abonnement à l'événement s'y retient - donc pour disposer du conteneur d'événements, les événements doivent être complètement enregistrés.


Vous avez quelque chose de mélangé. La "liste d'abonnement" n'est pas différente de toute autre collection (par exemple, votre arraylist standard) et c'est membre de la classe qui a l'événement. Donc, si toutes les références à cette classe sont perdues, des références de la liste des abonnements sont donc des références et des deux objets sont collectés ensemble. En outre, Googling sur vos mots-clés m'a donné cela, tout en soutenant complètement ce que j'ai énoncé ci-dessus: INTERACT-SW.CO.UK/IANGBLOG/2004/07/07/circularEventrefs


Vos liens disent également la même chose que moi. Le conteneur peut garder les abonnés vivants; Les abonnés ne peuvent pas garder le conteneur vivant.


@Vilx Comme je l'ai mentionné, je ne suis pas sûr sur les spécificités - mais des fuites de mémoire des événements enregistrés laissés enregistrés font . Cependant, je vais admettre que je ne sais pas comment ce problème se comporte avec une relation parent-enfant.


@Vilx ah je vois, alors j'étais mal comprendre la direction du problème - je vais mettre à jour ma réponse.


Il y a une fuite commune, mais c'est ce modèle: a.e + = b.m; B = null; Gc.Collect (); et ici les gens se demandent souvent pourquoi b n'est pas collecté. C'est un scénario typique, facile à manquer. J'ai eu des insectes comme ça aussi (était jolie "amusant" de le chasser ...) Mais cela ne fonctionne pas dans l'autre sens, c'est un mythe propagé par des gens qui ne comprennent pas vraiment ce que c'est tout à propos de .


@Vilx Eh bien, j'ai mis à jour ma réponse maintenant, alors je ne suis plus coupable de répandre le mythe. Cependant, c'est encore dangereux, les gens ne savent souvent pas que soit existe - le mythe ou la vérité. Celui qui m'a mis en surligné était une université qui a utilisé C # pour conduire une voiture, après 3 heures, il échouerait - une fuite de mémoire s'accumule lentement des abonnés suspendus lol


Merci! Maintenant, je me sens comme si j'ai fait mon Bonne acte de la journée . :RÉ


Faites une autre bonne action et laissez tomber le bowvote lol, eh bien, si c'était vous ;-)



1
votes

Quand il n'y a plus de références plus strictes à un objet particulier sur le tas (c'est-à-dire le nombre de références s'est allé à zéro), le GC est en effet programmé pour libérer la mémoire de l'objet. Peu importe où ou comment ces références sont définies; ils sont suivis par le GC et traités en conséquence.

Noter cependant: la GC est très non déterministe de nature (elle fonctionne sur un fil séparé, pour un démarrage) et peut ne pas se déplacer pour détruire l'objet en mémoire pendant un certain temps. "Un peu de temps" est généralement presque instantané, sauf dans des cas inhabituels où le processeur est trop occupé et / ou si beaucoup de mémoire sont libérés à la fois.


5 commentaires

Et comme Joralsav Jandek a écrit, GC est conscient de la référence circulaire et ne compte pas l'enfant comme un compte de référence?


Dans l'exemple que vous avez présenté, il n'y a pas de référence circulaire, comme je le vois ... même s'il y en avait, le GC n'a pas vraiment à gérer des références circulaires spécialement, comme je le sais. Un nombre de référence pour chaque objet est tout à fait suffisant.


Les parents références L'enfant et dans la liste des événements de l'enfant sont une référence au parent, de l'informer de SomeEvent. Me semble comme une circulaire. Ai-je tort?


Oh désolé, j'ai ignoré où le gestionnaire d'événements a été défini. Donc, il est circulaire à cet égard, bien que cela ne fait pas vraiment une différence comme je l'ai dit. Consultez mon commentaire sur l'article d'Adam s'il vous plaît.


Les références circulaires posent un problème pour GC qui repose sur des chiffres de référence. Par exemple, VB6 utilise GC basé sur le nombre et vous devez nettoyer les références circulaires si vous souhaitez empêcher les fuites de mémoire. Python a une manipulation spéciale pour des références circulaires. Cela n'est pas pertinent pour le GC de .NET, cependant, car il n'est pas basé sur des chiffres de référence, il est basé sur la traçage "accessibilité" lors de la passe GC.



1
votes
anotherClass.SomeEvent += AnotherClass_SomeEvent;

1 commentaires

Oui, je suis conscient du scénario que vous décrivez