web-dev-qa-db-fra.com

Libérer explicitement la mémoire en c #

J'ai créé une application c # qui utilise jusqu'à 150 Mo de mémoire (octets privés), principalement à cause d'un gros dictionnaire:

Dictionary<string, int> Txns = new Dictionary<string, int>();

Je me demandais comment libérer cette mémoire. J'ai essayé ceci:

Txns = null;
GC.Collect();

Mais cela ne semble pas faire beaucoup d'effet sur mes octets personnels - ils passent de 155 Mo à 145 Mo . Des indices?

Merci

-modifier-

D'accord, j'ai plus de chance avec ce code (les octets privés sont réduits à 50 Mo), mais pourquoi?

Txns.Clear(); // <- makes all the difference
Txns = null;
GC.Collect();

-modifier-

OK pour tous ceux qui disent «n'utilisez pas GC.collect», c'est assez correct (je ne vais pas en débattre, à part dire que vous pouvez voir mes antécédents C se manifester), mais cela ne répond pas vraiment à ma question: Pourquoi le ramasse-miettes ne libère-t-il la mémoire que si j'efface d'abord la liste des transactions? Cela ne devrait-il pas libérer la mémoire de toute façon, puisque le dictionnaire a été déréférencé?

36
Chris

Les octets privés reflètent l'utilisation de la mémoire du processus. Lorsque les objets sont collectés, le segment de mémoire associé peut ou non être libéré dans le système d'exploitation. Le CLR gère la mémoire au niveau du système d'exploitation. Etant donné que l'allocation et la libération de mémoire ne sont pas libres, il n'y a aucune raison de libérer chaque partie de mémoire immédiatement car il est probable que l'application demandera probablement plus de mémoire plus tard. 

17
Brian Rasmussen

si vous appelez GC.Collect (), il commence à faire son travail, mais il retourne immédiatement, il ne bloque pas, donc vous ne voyez pas son effet, si vous appelez simplement GC.WaitForPendingFinalizers () après cela, il bloquera votre application jusqu'à GC. Collect () termine son travail

11
Sadegh

Très probablement, vous avez une référence cachée au dictionnaire ailleurs. Ainsi, le dictionnaire n’est pas collecté, mais si vous _Clear(), le contenu est collecté.

Comme d'autres l'ont déjà noté, forcer le GC n'est pas recommandé. Cela pourrait amener la mémoire à être poussée dans des "générations" supérieures qui ne sont pas souvent collectées, gaspillant ainsi plus de mémoire que ce que l'on en gagne à long terme.

5
David Schmitt

Pas sûr de mémoire si Dictionary a un Dispose() ou non, mais il y a forcément un Clear(). Appelez l'une de ces personnes avant de définir des références à null.

Ensuite, laissez simplement le ramasse-miettes faire son travail. C’est presque jamais une bonne idée d’appeler explicitement vous-même le GC.Collect(). L'analyse de code statique (= FxCop) ne vous avertit pas avec Règle de fiabilité CA2001 à ce sujet pour rien, vous savez? Ne le faites tout simplement pas à moins de savoir vraiment ce que vous faites. Et même alors ne le fais pas. ;-)

Êtes-vous sûr que le dictionnaire est si énorme? N'est-ce pas juste 10 Mo de mémoire et le reste est pris par votre application? Question qui pourrait vous aider: Avez-vous déjà utilisé un profileur pour voir où la mémoire est réellement utilisée ...?

4
peSHIr

Avez-vous besoin de récupérer de la mémoire? La mémoire est disponible, elle n'est tout simplement pas récupérée. Vous ne devez pas effacer le dictionnaire, tenez une référence faible et laissez le runtime faire son travail.

Si vous voulez vraiment savoir ce qui se passe, consultez le .NET Memory Profiler . Cela vous donnera une visibilité sur ce qui se passe exactement avec votre objet, sur sa génération, sur quelle mémoire est utilisée, etc. :)

1
JP Alioto

Ok, j'ai une théorie ici .... Le dictionnaire est une collection de KeyValuePair qui est encore un type de référence.

Votre dictionnaire contient ces keyValuePairs. Quand tu dis:

Txns = null

Il libère la référence 'Txns' de la collection KeyValuePair. Mais la mémoire actuelle de 150 Mo est référencée par ces KeyValuePair et ils sont dans la portée, donc pas prêts pour la récupération de place.

Mais quand vous utilisez ce qui suit:

Txns.Clear();
Txns = null;
GC.Collect();

Ici, la méthode clear libère également les 150 Mo de données de leurs références d'objet KeyValuePair respectives. Ainsi, ces objets étaient prêts pour la récupération de place.

C'est juste une conjecture sauvage que je fais ici. Les commentaires sont les bienvenus :)

1
Savaratkar

Modifier:

Pour être juste, définir la référence sur null ne libère pas la mémoire, il attribue son conteneur à une adresse différente, dans le cas présent null. Selon MSDN , l'appel de Clear() a pour effet: "La propriété Count est définie sur 0 et les références à d'autres objets d'éléments de la collection sont également libérées. La capacité reste inchangée."

...

Vous ne devriez jamais appeler le ramasse-miettes. Vous utilisez des objets gérés sans ressources natives, faites confiance au ramasse-miettes pour le nettoyer après vous.

Outre la taille de votre dictionnaire, vous n'avez pas à vous soucier de la mémoire. La mémoire n'est pas votre problème, c'est le problème des éboueurs.

L'appel de Clear() supprimera les références à tout objet contenu à l'intérieur, mais la capacité restera inchangée.

Sur un plan technique, la collecte de mémoire est une opération coûteuse et longue. Raison d'être, non seulement le GC gère la mémoire de tas et nettoie-t-il votre tas, il défragmente également le tas. Il essaie de déplacer la mémoire dans des blocs contigus pour accélérer l'allocation lorsqu'un morceau de code fait une requête volumineuse.

p.s. Quelle est la taille de votre dictionnaire pour lequel vous utilisez 155 Mo de mémoire?

1
Chris

Windows a deux événements de disponibilité de la mémoire. Je m'attendrais à ce que le CLR réponde à cela. S'il y a beaucoup de mémoire disponible, le mieux est de NE PAS exécuter le ramasse-miettes. Par conséquent, pour vous assurer que vous observez bien le mauvais comportement du CLR, veuillez répéter ce test avec une autre application factice utilisant beaucoup de mémoire.

0
MSalters