Melhores práticas para forçar a coleta de lixo em C #


118

Na minha experiência, parece que a maioria das pessoas dirá que não é sensato forçar uma coleta de lixo, mas em alguns casos em que você está trabalhando com objetos grandes que nem sempre são coletados na geração 0, mas onde a memória é um problema, ok forçar a coleta? Existe uma prática recomendada para fazer isso?

Respostas:


112

A prática recomendada é não forçar uma coleta de lixo.

De acordo com o MSDN:

"É possível forçar a coleta de lixo chamando Collect, mas na maioria das vezes, isso deve ser evitado porque pode criar problemas de desempenho."

No entanto, se você puder testar seu código de maneira confiável para confirmar que chamar Collect () não terá um impacto negativo, vá em frente ...

Apenas tente garantir que os objetos sejam limpos quando você não precisar mais deles. Se você tiver objetos personalizados, observe o uso de "instrução usando" e a interface IDisposable.

Este link tem alguns bons conselhos práticos com relação à liberação de memória / coleta de lixo, etc:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx


3
Além disso, você pode definir diferentes msdn.microsoft.com/en-us/library/bb384202.aspx do LatencyMode
David d C e Freitas

4
Se seus objetos apontam para memória não gerenciada, você pode informar o coletor de lixo por meio do GC.AddMemoryPressure Api ( msdn.microsoft.com/en-us/library/… ). Isso fornece ao coletor de lixo mais informações sobre o seu sistema, sem interferir nos algoritmos de coleta.
Govert

+1: * veja como usar a "declaração usando" e a interface IDisposable. * Eu nem consideraria forçar, exceto como último recurso - um bom conselho (leia como 'isenção de responsabilidade'). Estou, no entanto, forçando a coleta em um teste de unidade para simular a perda de uma referência ativa em uma operação de back-end - em última análise, jogando um TargetOfInvocationNullException.
IAbstract 01 de

33

Veja desta forma - é mais eficiente jogar fora o lixo da cozinha quando a lata de lixo está a 10% ou deixá-la encher antes de levá-la para fora?

Ao não deixá-lo encher, você está perdendo seu tempo indo e voltando da lixeira do lado de fora. Isso é análogo ao que acontece quando o encadeamento do GC é executado - todos os encadeamentos gerenciados são suspensos enquanto ele está em execução. E, se não me engano, o encadeamento GC pode ser compartilhado entre vários AppDomains, de forma que a coleta de lixo afeta todos eles.

Claro, você pode encontrar uma situação em que não adicionará nada à lata de lixo tão cedo - digamos, se você for tirar férias. Então, seria uma boa ideia jogar o lixo fora antes de sair.

Esta PODE ser uma vez que forçar um GC pode ajudar - se o seu programa ficar inativo, a memória em uso não é coletada como lixo porque não há alocações.


5
Se você tivesse um bebê que morreria se você o deixasse por mais de um minuto e só tivesse um minuto para cuidar do lixo, faria um pouco de cada vez em vez de fazer tudo de uma vez. Infelizmente, o método GC :: Collect () não é mais rápido quanto mais você o chama. Então, para um mecanismo de tempo real, se você não pode simplesmente usar o mecanismo de descarte e deixar o GC agrupar seus dados, então você não deve usar um sistema gerenciado - de acordo com minha resposta (provavelmente abaixo deste, lol).
Jin

1
No meu caso, estou executando um algoritmo A * (caminho mais curto) REPETIDAMENTE para ajustar o desempenho ... que, na produção, será executado apenas uma vez (em cada "mapa"). Portanto, quero que o GC seja feito antes de cada iteração, fora do meu "bloco de medição de desempenho", pois sinto que modela mais de perto a situação em produção, na qual o GC poderia / deveria ser forçado após navegar em cada "mapa".
corlettk

Chamar o GC antes do bloco medido, na verdade, não modela a situação na produção, porque na produção o GC será feito em tempos imprevisíveis. Para atenuar isso, você deve fazer uma medição longa que incluirá várias execuções de GC e fatorar os picos durante a GC em sua análise e estatísticas.
Executado em

32

A melhor prática é não forçar uma coleta de lixo na maioria dos casos. (Todos os sistemas em que trabalhei que tinham coleta de lixo forçada, tinham problemas sublinhados que, se resolvidos, teriam removido a necessidade de forçar a coleta de lixo e acelerado muito o sistema.)

Existem alguns casos em que você sabe mais sobre o uso de memória do que o coletor de lixo. É improvável que isso seja verdade em um aplicativo multiusuário ou em um serviço que está respondendo a mais de uma solicitação por vez.

No entanto, em alguns tipos de processamento em lote você sabe mais do que o GC. Por exemplo, considere um aplicativo que.

  • Recebe uma lista de nomes de arquivos na linha de comando
  • Processa um único arquivo e grava o resultado em um arquivo de resultados.
  • Durante o processamento do arquivo, cria muitos objetos interligados que não podem ser coletados até que o processamento do arquivo seja concluído (por exemplo, uma árvore de análise)
  • Não mantém muito estado entre os arquivos que processou .

Você pode fazer um teste (após um teste cuidadoso) de que deve forçar uma coleta de lixo completa depois de processar cada arquivo.

Outro caso é um serviço que acorda a cada poucos minutos para processar alguns itens e não mantém nenhum estado enquanto está dormindo . Então, forçar uma coleção completa antes de dormir pode valer a pena.

A única vez em que consideraria forçar uma coleção é quando sei que muitos objetos foram criados recentemente e poucos objetos estão referenciados atualmente.

Eu preferiria ter uma API de coleta de lixo quando pudesse dar dicas sobre esse tipo de coisa sem ter que forçar um GC para mim.

Veja também " Dicas de desempenho de Rico Mariani "


2
Analogia: Playschool (sistema) mantém lápis de cor (recursos). Dependendo do número de crianças (tarefas) e da escassez de cores, o professor (.Net) decide como alocar e compartilhar entre as crianças. Quando uma cor rara é solicitada, o professor pode alocar do pool ou procurar por uma que não esteja sendo usada. O professor pode coletar periodicamente lápis não utilizados (coleta de lixo) para manter as coisas organizadas (otimizar o uso dos recursos). Geralmente um pai (programador) não pode predeterminar a melhor política de arrumação do giz de cera na sala de aula. A soneca programada de uma criança provavelmente não será um bom momento para interferir na coloração das outras crianças.
AlanK

1
@AlanK, gosto disso, pois quando as crianças vão para casa passar o dia, é um momento muito bom para o professor ajudante fazer uma boa arrumação sem que as crianças atrapalhem. (Sistemas recentes em que trabalhei acabaram de reiniciar o processo de serviço nessas ocasiões em vez de forçar o GC.)
Ian Ringrose

21

Acho que o exemplo dado por Rico Mariani foi bom: pode ser apropriado acionar um GC se houver uma mudança significativa no estado do aplicativo. Por exemplo, em um editor de documentos, pode ser OK acionar um GC quando um documento é fechado.


2
Ou antes de abrir um grande objeto contíguo que apresentou histórico de falhas e não apresenta resolução eficiente para aumentar sua granularidade.
crokusek

17

Existem algumas diretrizes gerais de programação que são absolutas. Metade do tempo, quando alguém diz 'você está fazendo errado', eles estão apenas cuspindo uma certa quantidade de dogma. Em C, costumava haver medo de coisas como código ou threads de automodificação, em linguagens de GC é forçar o GC ou, alternativamente, impedir que o GC seja executado.

Como é o caso da maioria das diretrizes e boas regras (e boas práticas de design), existem raras ocasiões em que faz sentido contornar a norma estabelecida. Você tem que ter certeza de que entende o caso, de que seu caso realmente requer a revogação da prática comum e de que entende os riscos e efeitos colaterais que pode causar. Mas existem casos assim.

Os problemas de programação são amplamente variados e requerem uma abordagem flexível. Já vi casos em que faz sentido bloquear GC em linguagens com coleta de lixo e lugares onde faz sentido acioná-lo em vez de esperar que ocorra naturalmente. 95% das vezes, qualquer um desses seria um sinal de não ter abordado o problema corretamente. Mas, uma vez em cada 20, provavelmente há um caso válido a ser defendido.


12

Aprendi a não tentar ser mais esperto que a coleta de lixo. Com isso dito, eu apenas continuo usando usingpalavras-chave ao lidar com recursos não gerenciados, como E / S de arquivo ou conexões de banco de dados.


27
compilador? o que o compilador tem a ver com GC? :)
KristoferA

1
Nada, apenas compila e otimiza o código. E isso definitivamente não tem nada a ver com o CLR ... ou mesmo .NET.
Kon

1
Objetos que ocupam muita memória, como imagens muito grandes, podem acabar não recebendo a coleta de lixo, a menos que você os faça explicitamente. Acho que isso (objetos grandes) era o problema do OP, mais ou menos.
code4life de

1
Envolvê-lo em um uso garantirá que ele seja agendado para GC quando estiver fora do escopo de uso. A menos que o computador exploda, essa memória provavelmente será limpa.
Kon

9

Não tenho certeza se é uma prática recomendada, mas ao trabalhar com grandes quantidades de imagens em um loop (ou seja, criando e descartando muitos objetos Graphics / Image / Bitmap), eu regularmente deixo o GC.Collect.

Acho que li em algum lugar que o GC só funciona quando o programa está (principalmente) ocioso, e não no meio de um loop intensivo, de modo que pode parecer uma área onde o GC manual pode fazer sentido.


Tem certeza de que precisa disso? O GC irá coletar se ele precisa de memória, mesmo se o seu código não é ocioso.
Konrad Rudolph,

Não tenho certeza de como é no .net 3.5 SP1 agora, mas anteriormente (1.1 e eu acredito que testei com 2.0) ele fez uma diferença no uso de memória. O GC, é claro, sempre coletará quando necessário, mas você ainda pode acabar perdendo 100 Megs de RAM quando você só precisa de 20. No entanto, precisaria de mais alguns testes
Michael Stum

2
O GC é acionado na alocação de memória quando a geração 0 atinge um certo limite (por exemplo 1 MB), não quando "algo está ocioso". Caso contrário, você poderia acabar com OutOfMemoryException em um loop simplesmente alocando e descartando objetos imediatamente.
liggett78

5
os 100megs de RAM não são desperdiçados se nenhum outro processo precisar. Está dando a você um bom aumento de desempenho :-P
Orion Edwards

9

Um caso que encontrei recentemente que exigia chamadas manuais para GC.Collect()foi ao trabalhar com grandes objetos C ++ que eram agrupados em pequenos objetos C ++ gerenciados, que por sua vez eram acessados ​​a partir do C #.

O coletor de lixo nunca foi chamado porque a quantidade de memória gerenciada usada era insignificante, mas a quantidade de memória não gerenciada usada era enorme. Chamar manualmente Dispose()os objetos exigiria que eu acompanhasse quando os objetos não são mais necessários, enquanto a chamada GC.Collect()limpará todos os objetos que não são mais referidos .....


6
A melhor maneira de resolver isso é chamando GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)o construtor e, posteriormente GC.RemoveMemoryPressure(addedSize), o finalizador. Desta forma, o coletor de lixo será executado automaticamente, levando em consideração o tamanho das estruturas não gerenciadas que podem ser coletadas. stackoverflow.com/questions/1149181/…
HugoRune

E uma maneira ainda melhor de resolver o problema é chamar Dispose (), o que você deve fazer de qualquer maneira.
fabspro

2
A melhor maneira é usar a estrutura Using. Tente / Finalmente .Dispor é um incômodo
TamusJRoyce

7

Acho que você já listou as melhores práticas e NÃO usá-las a menos que seja REALMENTE necessário. Eu recomendo fortemente examinar seu código com mais detalhes, usando ferramentas de criação de perfil, se necessário, para responder a essas perguntas primeiro.

  1. Você tem algo em seu código que está declarando itens em um escopo maior do que o necessário
  2. O uso de memória é realmente muito alto?
  3. Compare o desempenho antes e depois de usar GC.Collect () para ver se realmente ajuda.

5

Suponha que seu programa não tenha vazamento de memória, objetos se acumulam e não podem ser GC-ed em Gen 0 porque: 1) Eles são referenciados por muito tempo, então entre em Gen1 e Gen2; 2) Eles são objetos grandes (> 80K), então entre no LOH (Large Object Heap). E o LOH não compacta como em Gen0, Gen1 e Gen2.

Verifique o contador de desempenho de "Memória .NET" se você pode ver que 1) o problema não é realmente um problema. Geralmente, cada 10 GC Gen0 irá acionar 1 GC Gen1 e cada 10 GC Gen1 irá desencadear 1 GC Gen2. Teoricamente, GC1 e GC2 nunca podem ser GC-ed se não houver pressão no GC0 (se o uso de memória do programa estiver realmente conectado). Isso nunca acontece comigo.

Para o problema 2), você pode verificar o contador de desempenho da "Memória .NET" para verificar se o LOH está ficando inchado. Se for realmente um problema para o seu problema, talvez você possa criar um pool de objetos grandes, como este blog sugere http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx .


4

Objetos grandes são alocados no LOH (heap de objeto grande), não na geração 0. Se você está dizendo que eles não são coletados como lixo com a geração 0, você está certo. Eu acredito que eles são coletados apenas quando o ciclo completo de GC (gerações 0, 1 e 2) acontece.

Dito isso, acredito que, por outro lado, o GC ajustará e coletará memória de forma mais agressiva quando você trabalhar com objetos grandes e a pressão da memória aumentar.

É difícil dizer se devemos coletar ou não e em que circunstâncias. Eu costumava fazer GC.Collect () após descartar janelas / formulários de diálogo com vários controles etc. (porque no momento em que o formulário e seus controles terminam na geração 2 devido à criação de muitas instâncias de objetos de negócios / carregamento de muitos dados - não objetos grandes obviamente), mas na verdade não notou nenhum efeito positivo ou negativo a longo prazo.


4

Gostaria de acrescentar que: Chamar GC.Collect () (+ WaitForPendingFinalizers ()) é uma parte da história. Como corretamente mencionado por outros, GC.COllect () é uma coleção não determinística e é deixada ao critério do próprio GC (CLR). Mesmo se você adicionar uma chamada para WaitForPendingFinalizers, ela pode não ser determinística. Pegue o código deste link do msdn e execute o código com a iteração do loop do objeto como 1 ou 2. Você descobrirá o que significa não determinístico (definir um ponto de interrupção no destruidor do objeto). Precisamente, o destruidor não é chamado quando havia apenas 1 (ou 2) objetos remanescentes por Wait .. (). [Citation reqd.]

Se o seu código estiver lidando com recursos não gerenciados (ex: identificadores de arquivo externos), você deve implementar destruidores (ou finalizadores).

Aqui está um exemplo interessante:

Nota : Se você já experimentou o exemplo acima do MSDN, o código a seguir vai limpar o ar.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

Eu sugiro, primeiro analise qual poderia ser a saída e, em seguida, execute e, em seguida, leia o motivo abaixo:

{O destruidor só é chamado implicitamente quando o programa termina. } Para limpar deterministicamente o objeto, deve-se implementar IDisposable e fazer uma chamada explícita para Dispose (). Essa é a essência! :)


2

Mais uma coisa, acionar o GC Collect explicitamente pode NÃO melhorar o desempenho do seu programa. É bem possível piorar as coisas.

O .NET GC é bem projetado e ajustado para ser adaptável, o que significa que pode ajustar o limite GC0 / 1/2 de acordo com o "hábito" de uso de memória do programa. Assim, ele será adaptado ao seu programa após algum tempo de execução. Depois de invocar GC.Collect explicitamente, os limites serão redefinidos! E o .NET tem que gastar tempo para se adaptar ao "hábito" do seu programa novamente.

Minha sugestão é sempre confiar no .NET GC. Qualquer problema de memória surgir, verifique o contador de desempenho "Memória .NET" e diagnostique meu próprio código.


6
Eu acho que é melhor você mesclar esta resposta com a anterior.
Salamander2007

1

Não tenho certeza se é uma prática recomendada ...

Sugestão: não implemente isso ou nada quando não tiver certeza. Reavalie quando os fatos forem conhecidos e, em seguida, execute testes de desempenho antes / depois para verificar.


0

No entanto, se você puder testar seu código de maneira confiável para confirmar que chamar Collect () não terá um impacto negativo, vá em frente ...

IMHO, isso é semelhante a dizer "Se você pode provar que seu programa nunca terá nenhum bug no futuro, então vá em frente ..."

Com toda a seriedade, forçar o GC é útil para fins de depuração / teste. Se achar que precisa fazer isso em qualquer outro momento, ou você está enganado ou seu programa foi desenvolvido de maneira errada. De qualquer forma, a solução não é forçar o GC ...


"então ou você está enganado ou seu programa foi construído errado. De qualquer forma, a solução não é forçar o GC ..." Absolutos quase sempre não são verdadeiros. Existem algumas circunstâncias excepcionais em que faz sentido.
rola em
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.