9
votes

C # .NET GUARBAGE COLLECTION NON FONCTIONNANT?

Je travaille sur une solution relativement grande dans Visual Studio 2010. Il dispose de divers projets, l'un d'entre eux étant un projet de jeu XNA, et un autre étant un projet ASP.NET MVC 2.

Avec les deux projets, je suis confronté au même problème: après les commencer en mode de débogage, l'utilisation de la mémoire continue de se lever. Ils commencent à une utilisation de la mémoire de 40 et 100 Mo respectivement, mais les deux montées à 1,5 Go relativement rapidement (10 et 30 minutes respectivement). Après cela, il serait parfois revenir à la fermeture de l'utilisation initiale, et d'autres fois, il suffirait de lancer OutofMororyExceptions .

Bien sûr, cela indiquerait des fuites de mémoire sévères, de sorte que j'ai d'abord essayé de repérer le problème. Après avoir recherché des fuites sans succès, j'ai essayé d'appeler gc.collect () régulièrement (environ une fois par 10 secondes). Après avoir introduit ce "hack", l'utilisation de la mémoire est restée respectivement de 45 et 120 Mo pendant 24 heures (jusqu'à ce que j'ai arrêté de tester).

La collection des ordures de .NET est censée être "très bonne", mais je ne peux pas m'empêcher de soupçonner qu'il ne fait que son travail. J'ai utilisé le profileur CLR dans une tentative de dépannage de la question et j'ai montré que le projet XNA semblait avoir enregistré de nombreuses matrices d'octets que j'utilisais effectivement, mais que les références doivent déjà être supprimées et donc collectées par la poubelle Collecteur.

Encore une fois, lorsque j'appelle gc.collect () régulièrement, les problèmes d'utilisation de la mémoire semblent avoir disparu. Est-ce que quelqu'un sait ce qui pourrait causer cette utilisation haute mémoire? Est-il éventuellement lié à la course en mode débogage?


9 commentaires

Le même problème est-il répété en mode de libération?


Quelle était la taille des matrices d'octets individuels? Ils auraient pu être mis dans le grand tas d'objets, ce qui n'est pas collecté aussi souvent que tout le reste. Y avait-il une pression de mémoire sur le système?


Quelle est la valeur que vous utilisez pour "Usage de la mémoire"? Taille virtuelle? Octets privés?


@ Agent-J LOH Les objets sont collectés, mais GC ne défragment pas l'espace libre dans LOH afin que l'application puisse souffrir de problèmes de mémoire, même lorsqu'il n'y a pas beaucoup d'objets en mémoire.


Assurez-vous qu'ils sont vraiment de gros tableaux avant de passer aux conclusions. Sauf si vous voulez creuser dans l'esprit fils de grève et voyez quel tas ils sont dans. @Alexatnet, bon point sur la fragmentation. Je ne l'ai jamais vu, mais j'ai dû le chercher une ou deux fois.


@Alexatnet: Oui, j'ai couru dans cela seulement quelques semaines. Un bon profileur vous dira en termes simples où le problème est.


@pickypg & Luckeh: Oui, cela arrive en mode de libération aussi.


@ Agent-J: Les matrices d'octets individuels contiennent les données RGB 32 bits pour les textures générées à partir de cadres vidéo et de fichiers image. Donc, j'imagine qu'un seul tableau serait de 300 Ko - 4 Mo. Un nouveau tableau d'octets est créé pour chaque nouvelle texture que je chargée et éliminée (comme dans, définie sur NULL) dès que la texture a été chargée.


@Sven: J'ai bien peur de ne pas savoir exactement la différence. J'ai examiné l'utilisation de la mémoire dans Windows Task Manager.


6 Réponses :


5
votes

Tout d'abord, le GC fonctionne et fonctionne bien. Il n'y a pas d'insecte dans ce que vous venez de découvrir.

Maintenant que nous avons compris cela, certaines pensées:

  1. utilisez-vous trop de threads?
  2. N'oubliez pas que GC est non déterministe; Ça va courir chaque fois que cela pense qu'il doit être exécuté (même si vous appelez gc.collect () .
  3. Êtes-vous sûr que toutes vos références sont hors de portée?
  4. Qu'est-ce que vous chargez dans la mémoire en premier lieu? Grandes images? Grands fichiers texte?

    Votre profileur devrait vous dire ce qui utilise tant de mémoire. Commencez à couper au plus grand nombre de coupables que vous le pouvez.

    Aussi, appelant gc.collect () toutes les x secondes est une mauvaise idée et va peu probable à résoudre votre problème réel.


2 commentaires

- J'utilise un certain nombre de discussions, mais pas ce que j'appellerais "trop". Je ne pense pas que j'aurai plus de 5 threads fonctionnant à tout moment. - Je sais que GC fait fondamentalement ce qu'il veut, et GC.Collect () n'est qu'une suggestion à GC qu'il pourrait avoir des choses à nettoyer. Cependant, cela rend encore plus étrange qu'un appel de collection () résoudrait les problèmes de mémoire. - Mes références disparaissent définitivement, je fixe explicitement les références de tableau d'octets à NULL chaque fois que je n'en ai plus besoin. - Qu'est-ce que l'utilisation de la plupart des matières mémoire contenant des données de texture RGBA de 32 bits.


"Mes références disparaissent définitivement hors de portée, je fixe explicitement les références de tableau d'octets à NULL chaque fois que je n'en ai plus besoin" - sauf si bien sûr, il existe d'autres références à l'octet [] là. Cela dit, cela ressemble à la fragmentation de LOH. Un profileur vous dira bien.



4
votes

Analyser des problèmes de mémoire dans .NET n'est pas une tâche triviale et vous devriez définitivement lire plusieurs bons articles et essayer différents outils pour obtenir le résultat. Je me retrouve avec l'article suivant après des enquêtes: http: // www .alexatnet.com / Contenu / Net-Memory-Gestion-et-Garbage-Collector Vous pouvez également essayer de lire certains des articles de Jeffrey Richter, comme celui-ci: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx

De mon expérience, il y a deux raisons les plus courantes du problème hors mémoire:

  1. Les gestionnaires d'événements - ils peuvent contenir l'objet même lorsqu'il n'y a pas d'autres objets qui le référencent. Donc, idéalement, vous devez vous désabonner des gestionnaires d'événements pour détruire l'objet automatiquement.
  2. Le fil de finiseur est bloqué par un autre thread en mode STA. Par exemple, lorsque le fil de STA fonctionne beaucoup de travail, les autres threads sont arrêtés et que les objets figurant dans la file d'attente de finalisation ne peuvent pas être détruits.

3 commentaires

Je ne savais pas que sur les threads STA. En d'autres termes, dans WPF si le fil d'interface utilisateur est vraiment occupé, le GC ne peut pas collecter? Avez-vous un article que je pourrais lire sur cela?


Mais si appelant manuellement gc.collect résout le problème, il n'est évidemment pas que le GC ne peut pas recueillir les objets ou qu'il les considère comme étant enracinés, plus que Il pense que les collecter ne sont pas nécessaires.


@Lukeh: Exactement. Je me demande vraiment pourquoi il pense que les collecter n'est pas nécessaire, si le système jette une exception exceptionnelle ...



1
votes

J'ajouterais également que si vous effectuez un accès de fichiers, assurez-vous de fermer et / ou de disposer des lecteurs ou des écrivains. Vous devez avoir une correspondance 1-1 entre ouvrir n'importe quel fichier et la fermer.

En outre, j'utilise généralement l'utilisation de la clause pour des ressources, comme une connexion SQL: p>

using (var connection = new SqlConnection())
{
  // Do sql connection work in here.
}


3 commentaires

Bon conseil en général, mais Idisposable n'a rien à voir avec la distribution de la mémoire gérée.


Oui, je suis complètement d'accord avec toi là-bas. Mais je ne supposerais pas qu'ils n'utilisent que des ressources gérées. En fait, dès que je lisais Xna jeu, je me suis immédiatement interrogé sur les ressources non gérées qu'ils pourraient utiliser.


La fermeture des flux io est quelque chose que je fais toujours - mais merci pour le pointeur de toute façon.



16
votes

2 commentaires

Merci, post très utile. Je ne savais pas que les gestionnaires d'événements empêchent les objets d'être collectés, je vais certainement examiner cela. Je posterai les résultats plus tard.


J'ai déchargé quelques manipulateurs d'événements qui étaient toujours utilisés, comme vous l'avez suggéré. Cela a apporté de manière significative l'utilisation de la mémoire. Merci pour l'aide.



3
votes

EDIT: Ajout du lien vers fragmentation de gros tas d'objets .

Edit: Comme on dirait que c'est un problème d'allocation et de lancement des textures, pouvez-vous utiliser texture2d.setdata pour réutiliser le grand octet [] S?

Tout d'abord, vous devez déterminer s'il est géré ou une mémoire non gérée qui fuit.

  1. Utilisez perfmon pour voir ce qui arrive à votre processus 'Numéro de mémoire # Nombre d'octets dans tous les tas' et Process \ Bytes privés . Comparez les chiffres et la mémoire augmente. Si la hausse des octets privés dépasse la montée de la mémoire du tas, il s'agit de la croissance de la mémoire non gérée.

  2. La croissance de la mémoire non gérée indiquait que des objets ne sont pas disposés (mais éventuellement collectés lorsque leur finisseur s'exécute).

  3. S'il est géré la croissance de la mémoire, nous devrons voir quelle génération / loh (il y a aussi des compteurs de performance pour chaque génération d'octets de tas).

  4. Si c'est de grands octets de tas, vous voudrez reconsidérer l'utilisation et jeter de grandes matrices d'octets. Peut-être que les matrices d'octets peuvent être réutilisées au lieu de rejeter. En outre, envisagez d'affecter de grandes matrices d'octets qui sont des pouvoirs de 2. De cette façon, lorsque vous disposez, vous laisserez un grand "trou" dans le grand tas d'objets pouvant être rempli par un autre objet de la même taille. < / li>

  5. Une préoccupation finale est la mémoire épinglée, mais je n'ai aucun conseil à ce sujet parce que je n'ai jamais salué avec elle.


2 commentaires

Ajout de la solution proposée: réutiliser des objets Texture2D.


J'utilise déjà Setdata, et depuis d'autres occasions. N'avait-il pas pensé à réutiliser des objets Texture2D, merci.



0
votes

Le GC ne tient pas compte du tas non géré. Si vous créez de nombreux objets qui ne sont que des wrappers en C # à une mémoire non gérée plus importante, votre mémoire est dévourée, mais le GC ne peut pas prendre de décisions rationnelles en fonction de cela, car il ne voit que le tas de géré.

Vous vous retrouvez dans Une situation où le collectionneur de GC ne pense pas que vous soyez à court de mémoire, car la plupart des choses de votre tas de Yone 1 sont de 8 références d'octets où ils sont en réalité comme des icebergs en mer. La plupart de la mémoire est ci-dessous!

Vous pouvez utiliser ces appels GC: xxx

Ces méthodes permettent au collecteur des ordures de voir la mémoire non gérée ( Si vous lui fournissez les bonnes figures)


0 commentaires