41 votes

Comment faire en sorte que .NET récupère les déchets de manière agressive?

J'ai une application qui est utilisée dans le traitement de l'image, et je me retrouve généralement l'allocation des tableaux dans le 4000x4000 ushort taille, ainsi que les occasionnels flotteur et le comme. Actuellement, l' .NET framework a tendance à se bloquer dans cette application, apparemment au hasard, presque toujours avec une erreur de mémoire insuffisante. 32 mo n'est pas une énorme déclaration, mais si .NET est la fragmentation de la mémoire, alors il est très possible que ces grandes continu allocations de ne pas se comporter comme prévu.

Est-il un moyen de dire à la poubelle collecteur à être plus agressifs, ou à la défragmentation de la mémoire (si c'est ça le problème)? Je me rends compte qu'il y a de la GC.Recueillir et GC.WaitForPendingFinalizers appels, et j'ai saupoudré assez généreusement par le biais de mon code, mais je suis quand même à faire des erreurs. Il est peut-être parce que je suis d'appeler les routines dll que l'utilisation de code natif beaucoup, mais je ne suis pas sûr. J'ai dépassé que du code C++, et assurez-vous que la mémoire je déclare que je supprime, mais encore obtenir ces C# se bloque, donc je suis sûr que ce n'est pas là. Je me demande si le C++ appels pourraient interférer avec la GC, le rendant laisser derrière mémoire, car une fois interagi avec un appel des indigènes, est-ce possible? Si oui, puis-je activer cette fonctionnalité off?

EDIT: Voici des code qui sera la cause de la panne. Selon ce DONC, la question, je n'ai pas besoin d'être mise au rebut de la BitmapSource objets ici. Voici la version naïve, pas de GC.Recueille en elle. Généralement, il se bloque à l'itération 4 à 10 de l'annulation de la procédure. Ce code remplace le constructeur dans un vide projet WPF, depuis que je suis en utilisant WPF. Je ne les délires avec les bitmapsource en raison des limitations je l'ai expliqué dans ma réponse à @dthorpe ci-dessous ainsi que les conditions énumérées dans cette SORTE de question.

public partial class Window1 : Window {
    public Window1() {
        InitializeComponent();
        //Attempts to create an OOM crash
        //to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
        int theRows = 4000, currRows;
        int theColumns = 4000, currCols;
        int theMaxChange = 30;
        int i;
        List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack
        byte[] displayBuffer = null;//the buffer used as a bitmap source
        BitmapSource theSource = null;
        for (i = 0; i < theMaxChange; i++) {
            currRows = theRows - i;
            currCols = theColumns - i;
            theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
            displayBuffer = new byte[theList[i].Length];
            theSource = BitmapSource.Create(currCols, currRows,
                    96, 96, PixelFormats.Gray8, null, displayBuffer,
                    (currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
            System.Console.WriteLine("Got to change " + i.ToString());
            System.Threading.Thread.Sleep(100);
        }
        //should get here.  If not, then theMaxChange is too large.
        //Now, go back up the undo stack.
        for (i = theMaxChange - 1; i >= 0; i--) {
            displayBuffer = new byte[theList[i].Length];
            theSource = BitmapSource.Create((theColumns - i), (theRows - i),
                    96, 96, PixelFormats.Gray8, null, displayBuffer,
                    ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
            System.Console.WriteLine("Got to undo change " + i.ToString());
            System.Threading.Thread.Sleep(100);
        }
    }
}

Maintenant, si je suis explicite dans l'appel de l'garbage collector, j'ai pour envelopper l'ensemble du code dans une boucle externe à cause de la OOM crash. Pour moi, cela a tendance à se produire autour de x = 50:

public partial class Window1 : Window {
    public Window1() {
        InitializeComponent();
        //Attempts to create an OOM crash
        //to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
        for (int x = 0; x < 1000; x++){
            int theRows = 4000, currRows;
            int theColumns = 4000, currCols;
            int theMaxChange = 30;
            int i;
            List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack
            byte[] displayBuffer = null;//the buffer used as a bitmap source
            BitmapSource theSource = null;
            for (i = 0; i < theMaxChange; i++) {
                currRows = theRows - i;
                currCols = theColumns - i;
                theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
                displayBuffer = new byte[theList[i].Length];
                theSource = BitmapSource.Create(currCols, currRows,
                        96, 96, PixelFormats.Gray8, null, displayBuffer,
                        (currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
            }
            //should get here.  If not, then theMaxChange is too large.
            //Now, go back up the undo stack.
            for (i = theMaxChange - 1; i >= 0; i--) {
                displayBuffer = new byte[theList[i].Length];
                theSource = BitmapSource.Create((theColumns - i), (theRows - i),
                        96, 96, PixelFormats.Gray8, null, displayBuffer,
                        ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
                GC.WaitForPendingFinalizers();//force gc to collect, because we're in scenario 2, lots of large random changes
                GC.Collect();
            }
            System.Console.WriteLine("Got to changelist " + x.ToString());
            System.Threading.Thread.Sleep(100);
        }
    }
}

Si je suis une mauvaise manipulation de la mémoire dans les deux cas, si il y a quelque chose que je devrais place avec un profiler, laissez-moi savoir. C'est assez simple routine là.

Malheureusement, il ressemble à @Kevin réponse est juste, c'est un bug .NET et comment .NET pour gérer les objets de plus de 85k. Cette situation me paraît extrêmement étrange, pourrait Powerpoint être réécrit .NET avec ce genre de limitation, ou l'un des autres applications de bureautique? 85k ne me semble pas être un ensemble beaucoup d'espace, et j'avais aussi penser que tout programme qui utilise des soi-disant "grands" allocations fréquemment deviendrait instable dans une affaire de quelques jours à quelques semaines lors de l'utilisation .NET.

EDIT: Il semble que Kevin est droit, c'est une limitation de .NET GC. Pour ceux qui ne veulent pas suivre la totalité du fil, des .NET a quatre GC tas: gen0, gen1, gen2, et LOH (Objets Volumineux). Tout ce qui est 85k ou plus petit va sur l'un des trois premiers segments de mémoire, en fonction de l'heure de création (déplacé de gen0 à gen1 à gen2, etc). Des objets de plus de 85k placés sur la liturgie des heures. La liturgie des heures est jamais compacté, afin, éventuellement, de la répartition du type, je suis en train de faire finira par causer une OOM erreur comme des objets dispersés que l'espace mémoire. Nous avons trouvé que le passage à la .NET 4.0 ne aider un peu le problème, le fait de retarder l'exception, mais pas l'empêcher. Pour être honnête, cela se sent un peu comme le 640k barrière-- 85k devrait être suffisant pour n'importe quel utilisateur de l'application (pour paraphraser cette vidéo d'une discussion de la GC dans le .NET). Pour l'enregistrement, Java ne présentent pas ce comportement avec ses GC.

22voto

Paolo Points 11860

Commencez par un rétrécissement vers le bas où se trouve le problème. Si vous avez un natif de fuite de mémoire, piquer le GC ne va pas faire quelque chose pour vous.

Exécuter l'analyseur de performances et de regarder la .NET taille du segment de mémoire et d'Octets Privés compteurs. Si la taille de segment de mémoire reste assez constante, mais octets privés est croissante, alors vous avez un code natif question et vous aurez besoin pour briser le C++ outils de débogage.

En supposant que le problème est avec la .NET tas, vous devez exécuter un profiler à l'encontre du code comme Redgate de l'Ant profiler ou de JetBrain de DotTrace. Cela vous indiquera les objets qui prennent de l'espace et de ne pas être collectées rapidement. Vous pouvez également utiliser WinDbg avec SOS pour cela, mais c'est un système d'interface (puissante bien).

Une fois que vous avez trouvé les éléments en cause, il devrait être plus évident de savoir comment traiter avec eux. Certains le genre de choses qui peuvent causer des problèmes de champs statiques de référencement des objets, des gestionnaires d'événements n'étant pas non, des objets de vivre assez longtemps pour entrer dans Gen2 mais alors en train de mourir peu de temps après, etc etc. Sans un profil de la mémoire du tas, vous ne serez pas en mesure d'identifier la réponse.

Quoi que vous fassiez bien, "généreusement saupoudrer" GC.Appels à frais virés est presque toujours le mauvais chemin pour essayer de résoudre le problème.

Il ya une petite chance que le passage à la version du serveur de la GC permettrait d'améliorer les choses (juste une propriété dans le fichier de configuration) - la station de travail par défaut de la version est orientée vers le maintien d'une INTERFACE utilisateur réactive donc effectivement donner avec de grandes, longues colections.

22voto

Kevin Points 57797

Voici quelques articles détaillant les problèmes avec le Tas d'Objets Volumineux. Il semble que vous pourriez être en cours d'exécution dans.

http://connect.microsoft.com/VisualStudio/feedback/details/521147/large-object-heap-fragmentation-causes-outofmemoryexception

Les Dangers des tas d'objets volumineux:
http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/

Voici un lien sur la façon de recueillir des données sur le Tas d'Objets Volumineux (LOH):
http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

Selon les ce, il semble y avoir aucun moyen de compacter le LOH. Je ne peux pas trouver quelque chose de plus récent que dit explicitement comment le faire, et il semble donc qu'il n'a pas changé dans le runtime 2.0:
http://blogs.msdn.com/maoni/archive/2006/04/18/large-object-heap.aspx

La manière simple de gérer la question est de fabriquer de petits objets, si possible. Votre autre option est de créer seulement une poignée de grands objets et de les réutiliser encore et encore. Pas une idée de la situation, mais il pourrait être mieux que la ré-écriture de la structure de l'objet. Puisque vous avez dit que les objets créés (tableaux) sont de tailles différentes, il peut être difficile, mais il pourrait garder l'application de s'écraser.

4voto

Matthew Steeples Points 4637

Utilisez Process Explorer (de Sysinternals) pour voir ce qu'est le segment d'objets volumineux pour votre application. Votre meilleur pari sera de rendre vos tableaux plus petits mais d’en avoir plus. Si vous pouvez éviter d'allouer vos objets sur le LOH, vous n'obtiendrez pas les exceptions OutOfMemoryExceptions et vous ne devrez pas non plus appeler GC.Collect manuellement.

Le LOH n'est pas compacté et n'attribue que de nouveaux objets à la fin, ce qui signifie que vous pouvez rapidement manquer d'espace.

3voto

Aaronaught Points 73049

Si vous êtes à allouer une grande quantité de mémoire dans un non géré la bibliothèque (c'est à dire de la mémoire que le GC n'est pas au courant), alors vous pouvez faire le GC conscient de cela avec le GC.AddMemoryPressure méthode.

Bien sûr, cela dépend un peu de ce que le code non managé est en train de faire. Vous n'avez pas expressément déclaré que c'est l'allocation de mémoire, mais j'ai l'impression qu'il est. Si oui, alors c'est exactement ce que la méthode a été conçue pour. Et puis, si le non géré la bibliothèque est d'allouer beaucoup de mémoire ensuite, il est également possible que la fragmentation de la mémoire, qui est complètement au-delà de la GC de contrôle, même avec AddMemoryPressure. J'espère que c'est pas le cas; si elle l'est, vous aurez probablement à refactoriser la bibliothèque ou de changer la façon dont il est utilisé.

P. S. N'oubliez pas d'appeler la GC.RemoveMemoryPressure quand vous pouvez enfin libre de la mémoire non managée.

(P. P. S. Certains des autres réponses sont probablement raison, c'est beaucoup plus tendance à être tout simplement une fuite de mémoire dans votre code, surtout si c'est le traitement de l'image, j'avais à parier que vous n'êtes pas correctement au rebut votre IDIsposable des cas. Mais juste au cas où les réponses ne pas vous conduire n'importe où, c'est un autre chemin que vous pouvez prendre.)

2voto

dthorpe Points 23314

Juste une parenthèse: La .NET garbage collector effectue un "rapide" GC lorsqu'une fonction retourne à son appelant. Cela permettra d'éliminer les locaux de vars déclarée dans la fonction.

Si la structure de votre code tel que vous avez une grande fonction qui alloue des gros blocs de cours et plus dans une boucle, en assignant à chaque nouveau bloc de la même local var, le GC peut pas coup de pied dans les réclamer la non référencées blocs pour un certain temps.

Si, d'autre part, la structure de votre code tel que vous avez une fonction externe avec une boucle qui appelle une fonction interne, et la mémoire est allouée et l'attribution d'un local var dans l'intérieur de la fonction, le GC doit se faire immédiatement, lorsque la fonction interne renvoie à l'appelant et de récupérer le grand bloc de mémoire qui vient d'être attribué, parce que c'est un local var dans une fonction qui est de retour.

Éviter les tempation au mess avec le GC.Collecter de manière explicite.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X