Quando é aceitável ligar para GC.Collect?


166

O conselho geral é que você não deve ligar GC.Collectdo seu código, mas quais são as exceções a esta regra?

Só consigo pensar em alguns casos muito específicos em que pode fazer sentido forçar uma coleta de lixo.

Um exemplo que vem à mente é um serviço, que acorda a intervalos, realiza algumas tarefas e dorme por um longo tempo. Nesse caso, pode ser uma boa idéia forçar uma coleta para impedir que o processo que está prestes a ficar ocioso retenha mais memória do que o necessário.

Existem outros casos em que é aceitável ligar GC.Collect?


Respostas:


153

Se você tem um bom motivo para acreditar que um conjunto significativo de objetos - particularmente aqueles que você suspeita estar nas gerações 1 e 2 - agora são elegíveis para a coleta de lixo, e esse seria o momento apropriado para a coleta em termos do pequeno desempenho atingido .

Um bom exemplo disso é se você acabou de fechar um formulário grande. Você sabe que todos os controles da interface do usuário agora podem ser coletados como lixo e uma pausa muito curta à medida que o formulário é fechado provavelmente não será perceptível para o usuário.

ATUALIZAÇÃO 2.7.2018

A partir do .NET 4.5 - existe GCLatencyMode.LowLatencye GCLatencyMode.SustainedLowLatency. Ao entrar e sair de um desses modos, é recomendável forçar um GC completo GC.Collect(2, GCCollectionMode.Forced).

A partir do .NET 4.6 - existe o GC.TryStartNoGCRegionmétodo (usado para definir o valor somente leitura GCLatencyMode.NoGCRegion). Isso pode, por si só, executar uma coleta de lixo com bloqueio completo, na tentativa de liberar memória suficiente, mas, como estamos impedindo o GC por um período, eu diria que também é uma boa idéia executar o GC completo antes e depois.

Fonte: Engenheiro da Microsoft, Ben Watson: Writing .NET High-Performance Code , 2nd Ed. 2018.

Vejo:


8
De acordo com o código-fonte da Microsoft que chama GC.Collect (2) a cada 850ms, tudo bem. Não acredita? Em seguida, consulte PresentationCore.dll, MS.Internal.MemoryPressure.ProcessAdd (). Atualmente, tenho um aplicativo de processamento de imagens (imagens pequenas, nada com pressão real de memória) em que chamar GC.Collect (2) leva mais de 850 ms e, portanto, todo o aplicativo fica congelado por isso (o aplicativo gasta 99,7% do tempo no GC).
springy76

36
@ springy76: sendo feito pela Microsoft em um lugar não significa que ele é considerado uma coisa boa por aqueles dando conselhos da Microsoft ...
Jon Skeet

4
Eu não gosto desse exemplo. Qual o sentido de fazê-lo após o fechamento do formulário? Um bom exemplo que vejo é para depois de carregar o nível do jogo no XBox ou WindowsPhone. Nessas plataformas, o GC é executado depois de alocar 1 MB ou sth assim. Portanto, é bom alocar o máximo possível durante o carregamento do nível (enquanto mostra uma tela inicial) e, em seguida, faça o GC.Collect para tentar evitar coleções durante o jogo.
Piotr Perak

8
@Peri: O ponto de fazê-lo depois de forma estreita é que você acabou de fazer um monte de objetos (controles, os dados que foram visualizadas) elegível para coleta de lixo - lo chamando GC.Collectvocê está dizendo, basicamente, o coletor de lixo que você sabe melhor do que isso para variar. Por que você não gosta do exemplo?
precisa

6
@SHCJ: GC.Collect()solicitará que o GC realize uma coleção completa . Se você sabe que acabou de tornar elegíveis muitos objetos de vida anterior para a coleta de lixo e acredita que é menos provável que o usuário observe uma pequena pausa agora ou mais tarde, parece perfeitamente razoável pensar que agora é melhor hora de solicitar uma coleção do que deixá-la acontecer mais tarde.
precisa

50

Eu uso GC.Collectsomente ao escrever plataformas de teste de desempenho / criação de perfil bruto; ou seja, eu tenho dois (ou mais) blocos de código para testar - algo como:

GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestA(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestB(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
...

Assim que TestA()e TestB()executado com o maior estado semelhante quanto possível - ou seja, TestB()não se embebedar só porque TestAdeixou muito perto do ponto de inflexão.

Um exemplo clássico seria um exe de console simples (um Mainmétodo suficientemente ordenado para ser postado aqui, por exemplo), que mostra a diferença entre concatenação de strings em loop e StringBuilder.

Se eu precisar de algo preciso, então seriam dois testes completamente independentes - mas, com frequência, isso é suficiente se quisermos minimizar (ou normalizar) o GC durante os testes para ter uma ideia aproximada do comportamento.

Durante o código de produção? Ainda tenho que usá-lo ;-p


1
E eu provavelmente adiciono "WaitForPendingFinalizers" (ou o que quer que seja) também neste caso ;-p
Marc Gravell

29

A melhor prática é não forçar uma coleta de lixo na maioria dos casos. (Todos os sistemas nos quais trabalhei que forçaram a coleta de lixo, tiveram problemas sublinhados que, se resolvidos, eliminariam a necessidade de forçar a coleta de lixo e aceleraram bastante o sistema.)

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

No entanto, em algum processamento de tipo de lote, você conhece mais do que o GC. Por exemplo, considere uma aplicação que.

  • É fornecida uma lista de nomes de arquivos na linha de comando
  • Processa um único arquivo e depois 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 o estado de correspondência entre os arquivos processados .

Você poderá fazer um teste de caso (após cuidadoso) para forçar uma coleta de lixo completa depois de processar cada arquivo.

Outros casos é um serviço que acorda a cada poucos minutos para processar alguns itens e não mantém nenhum estado enquanto dorme . 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 muito poucos objetos são atualmente referenciados.

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 sozinho.

Veja também " Petiscos de desempenho da Rico Mariani "



11

Em grandes sistemas 24/7 ou 24/6 - sistemas que reagem a mensagens, solicitações de RPC ou que pesquisam um banco de dados ou processam continuamente - é útil ter uma maneira de identificar vazamentos de memória. Para isso, costumo adicionar um mecanismo ao aplicativo para suspender temporariamente qualquer processamento e executar a coleta de lixo completa. Isso coloca o sistema em um estado quieto em que a memória restante é legitimamente longa vida útil (caches, configuração, etc.) ou então é 'vazada' (objetos que não se espera ou que desejam que sejam enraizados, mas realmente são).

A existência desse mecanismo facilita muito o perfil do uso da memória, pois os relatórios não ficam embaçados com o ruído do processamento ativo.

Para ter certeza de obter todo o lixo, é necessário executar duas coleções:

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

Como a primeira coleção fará com que quaisquer objetos com finalizadores sejam finalizados (mas não na verdade colete esses objetos). O segundo GC coletará lixo esses objetos finalizados.


Eu já vi a coleção de duas passagens em alguns lugares agora, mas depois de ler a passagem na documentação do MSDN para GC.WaitForPendingFinalizers que diz: "Aguarde a conclusão de todos os finalizadores antes de continuar. Sem essa chamada para GC.WaitForPendingFinalizers, o loop de trabalho abaixo pode ser executado ao mesmo tempo que os finalizadores. Com essa chamada, o loop de trabalho é executado somente após todos os finalizadores terem sido chamados. " Eu sou apenas um toque paranóico. Você conhece uma fonte definitiva para fazer duas passagens?
jerhewet

1
@jerhewet: A chave para entender por que duas coleções são necessárias é entender o que acontece aos objetos com finalizadores. Infelizmente, não tenho exatamente o que você está pedindo, mas leia este artigo e esta pergunta no SO .
Paul Ruane

10

Você pode chamar GC.Collect () quando souber algo sobre a natureza do aplicativo que o coletor de lixo não conhece. É tentador pensar que, como autor, isso é muito provável. No entanto, a verdade é que o GC equivale a um sistema especialista bem escrito e testado, e é raro que você saiba algo sobre os caminhos de código de baixo nível que não conhece.

O melhor exemplo em que você pode ter informações extras é um aplicativo que alterna entre períodos de inatividade e períodos muito ocupados. Você deseja o melhor desempenho possível para os períodos de maior movimento e, portanto, deseja usar o tempo ocioso para fazer alguma limpeza.

No entanto, na maioria das vezes o GC é inteligente o suficiente para fazer isso de qualquer maneira.


8

Como uma solução de fragmentação de memória. Eu estava perdendo exceções de memória enquanto escrevia muitos dados em um fluxo de memória (lendo em um fluxo de rede). Os dados foram gravados em pedaços de 8K. Depois de atingir 128M, houve exceção, embora houvesse muita memória disponível (mas estava fragmentada). Ligar para GC.Collect () resolveu o problema. Eu era capaz de lidar com mais de 1G após a correção.


7

Dê uma olhada neste artigo de Rico Mariani. Ele fornece duas regras quando chamar GC.Collect (a regra 1 é: "Não"):

Quando ligar para GC.Collect ()


3
Já esteve lá. Não estou tentando encontrar desculpas para fazer algo que você não deve fazer, mas gostaria de saber se há casos específicos em que isso seria aceitável.
Brian Rasmussen

5

Uma instância em que é quase necessário chamar GC.Collect () é ao automatizar o Microsoft Office através da Interop. Os objetos COM do Office não gostam de ser liberados automaticamente e podem resultar em instâncias do produto do Office ocupando quantidades muito grandes de memória. Não tenho certeza se isso é um problema ou por design. Existem muitas postagens sobre esse tópico na Internet, então não vou entrar em muitos detalhes.

Ao programar usando a Interop, todos os objetos COM devem ser liberados manualmente, geralmente usando o Marshal.ReleseComObject (). Além disso, chamar a Garbage Collection manualmente pode ajudar a "limpar" um pouco. Chamar o código a seguir ao concluir com objetos de interoperabilidade parece ajudar bastante:

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

Na minha experiência pessoal, usar uma combinação de ReleaseComObject e chamar manualmente a coleta de lixo reduz bastante o uso de memória dos produtos do Office, especificamente o Excel.


Sim, eu encontrei isso com o .net acessando o Excel, que também funciona via objetos COM. É importante notar que isso não funcionará bem no modo DEBUG, porque as operações do GC são limitadas lá. Funcionará como pretendido apenas no modo RELEASE. link relevante: stackoverflow.com/questions/17130382/…
Blechdose

5

Eu estava fazendo alguns testes de desempenho na matriz e na lista:

private static int count = 100000000;
private static List<int> GetSomeNumbers_List_int()
{
    var lstNumbers = new List<int>();
    for(var i = 1; i <= count; i++)
    {
        lstNumbers.Add(i);
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Array()
{
    var lstNumbers = new int[count];
    for (var i = 1; i <= count; i++)
    {
        lstNumbers[i-1] = i + 1;
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Enumerable_Range()
{
    return  Enumerable.Range(1, count).ToArray();
}

static void performance_100_Million()
{
    var sw = new Stopwatch();

    sw.Start();
    var numbers1 = GetSomeNumbers_List_int();
    sw.Stop();
    //numbers1 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
    var numbers2 = GetSomeNumbers_Array();
    sw.Stop();
    //numbers2 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
//getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method
    var numbers3 = GetSomeNumbers_Enumerable_Range();
    sw.Stop();
    //numbers3 = null;
    //GC.Collect();

    Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds));
}

e cheguei OutOfMemoryExceptionno método GetSomeNumbers_Enumerable_Range, a única solução alternativa é desalocar a memória:

numbers = null;
GC.Collect();

Por que votar? minha resposta é um exemplo que demonstra quando ligar para o GC. Você tem uma sugestão melhor? Você é convidado a apresentar.
Daniel B

4

No seu exemplo, acho que chamar GC.Collect não é o problema, mas sim um problema de design.

Se você for acordar em intervalos, (horários definidos), seu programa deverá ser criado para uma única execução (executar a tarefa uma vez) e terminar. Em seguida, você configura o programa como uma tarefa agendada para execução nos intervalos agendados.

Dessa forma, você não precisa se preocupar em ligar para GC.Collect (o que você raramente deve fazer).

Dito isto, Rico Mariani tem um ótimo post sobre esse assunto, que pode ser encontrado aqui:

http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx


3

Um local útil para chamar GC.Collect () é em um teste de unidade quando você deseja verificar se não está criando um vazamento de memória (por exemplo, se você está fazendo algo com WeakReferences ou ConditionalWeakTable, código gerado dinamicamente, etc.).

Por exemplo, eu tenho alguns testes como:

WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);

Pode-se argumentar que o uso de WeakReferences é um problema por si só, mas parece que se você estiver criando um sistema que se baseia nesse comportamento, chamar GC.Collect () é uma boa maneira de verificar esse código.




2
using(var stream = new MemoryStream())
{
   bitmap.Save(stream, ImageFormat.Png);
   techObject.Last().Image = Image.FromStream(stream);
   bitmap.Dispose();

   // Without this code, I had an OutOfMemory exception.
   GC.Collect();
   GC.WaitForPendingFinalizers();
   //
}

2

Existem algumas situações em que é melhor prevenir do que remediar.

Aqui está uma situação.

É possível criar uma DLL não gerenciada em C # usando IL reescreve (porque há situações em que isso é necessário).

Agora, suponha, por exemplo, que a DLL crie uma matriz de bytes no nível da classe - porque muitas das funções exportadas precisam de acesso a elas. O que acontece quando a DLL é descarregada? O coletor de lixo é chamado automaticamente nesse ponto? Eu não sei, mas sendo uma DLL não gerenciada , é perfeitamente possível que o GC não seja chamado. E seria um grande problema se não fosse chamado. Quando a DLL é descarregada, também seria o coletor de lixo - quem será o responsável por coletar qualquer lixo possível e como eles o farão? Melhor empregar o coletor de lixo do C #. Tenha uma função de limpeza (disponível para o cliente DLL) em que as variáveis ​​no nível da classe estão definidas como nulas e o coletor de lixo chamado.

Melhor prevenir do que remediar.


2

Ainda estou bastante inseguro sobre isso. Trabalho há 7 anos em um servidor de aplicativos. Nossas instalações maiores utilizam RAM de 24 GB. Suas chamadas altamente multithreaded e ALL para GC.Collect () tiveram problemas de desempenho realmente terríveis.

Muitos componentes de terceiros usaram o GC.Collect () quando acharam que era inteligente fazer isso agora. Portanto, um simples grupo de relatórios do Excel bloqueou o servidor de aplicativos para todos os threads várias vezes por minuto.

Tivemos que refatorar todos os componentes de terceiros para remover as chamadas GC.Collect () e tudo funcionou bem depois disso.

Mas também estou executando servidores no Win32 e aqui comecei a usar muito o GC.Collect () depois de obter uma OutOfMemoryException.

Mas também não tenho certeza sobre isso, porque muitas vezes notei que quando recebo um OOM em 32 bits e tento executar a mesma operação novamente, sem chamar GC.Collect (), funcionou bem.

Uma coisa que me pergunto é a própria exceção do OOM ... Se eu tivesse escrito o .Net Framework e não posso alocar um bloco de memória, usaria GC.Collect (), desfragmentar a memória (??), tente novamente , e se ainda não consigo encontrar um bloco de memória livre, lançaria a exceção OOM.

Ou, pelo menos, torne esse comportamento como opção configurável, devido às desvantagens do problema de desempenho com o GC.Collect.

Agora eu tenho muitos códigos como este no meu aplicativo para "resolver" o problema:

public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2)
{

    int oomCounter = 0;
    int maxOOMRetries = 10;
    do
    {
        try
        {
            return func(a1, a2);
        }
        catch (OutOfMemoryException)
        {
            oomCounter++;
            if (maxOOMRetries > 10)
            {
                throw;
            }
            else
            {
                Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString());
                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10));
                GC.Collect();
            }
        }
    } while (oomCounter < maxOOMRetries);

    // never gets hitted.
    return default(TResult);
}

(Observe que o comportamento Thread.Sleep () é realmente um aplicativo específico para aplicativos, porque estamos executando um serviço de armazenamento em cache ORM, e o serviço leva algum tempo para liberar todos os objetos em cache, se a RAM exceder alguns valores predefinidos. alguns segundos na primeira vez e aumentou o tempo de espera a cada ocorrência de OOM.)


Um componente não deve chamar GC.Collect. Como ele tem um efeito amplo no aplicativo, somente o aplicativo deve fazê-lo (se houver).
CodesInChaos

If i would have written the .Net Framework, and i can't alloc a memory block, i would use GC.Collect(),- Acho que eles já estão fazendo isso - vi indicações de que um dos gatilhos internos do GC é a falha na alocação de memória.
G. Stoynev 9/07

2

Você deve evitar usar GC.Collect (), pois é muito caro. Aqui está um exemplo:

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet.Remove(RecordSet[0]);

        }
        GC.Collect(); // AVOID
    }

RESULTADO DO TESTE: USO DA CPU 12%

Quando você muda para isso:

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet[0].Dispose(); //  Bitmap destroyed!
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet[0].Dispose(); //  Bitmap destroyed!
            RecordSet.Remove(RecordSet[0]);

        }
        //GC.Collect();
    }

RESULTADO DO TESTE: USO DA CPU 2-3%


1

Outro motivo é quando você tem um SerialPort aberto em uma porta USB COM e o dispositivo USB é desconectado. Como o SerialPort foi aberto, o recurso mantém uma referência à porta conectada anteriormente no registro do sistema. O registro do sistema conterá dados obsoletos , portanto a lista de portas disponíveis estará incorreta. Portanto, a porta deve estar fechada.

Chamar SerialPort.Close () na porta chama Dispose () no objeto, mas permanece na memória até a coleta de lixo ser executada, fazendo com que o registro permaneça obsoleto até que o coletor de lixo decida liberar o recurso.

De https://stackoverflow.com/a/58810699/8685342 :

try
{
    if (port != null)
        port.Close(); //this will throw an exception if the port was unplugged
}
catch (Exception ex) //of type 'System.IO.IOException'
{
    System.GC.Collect();
    System.GC.WaitForPendingFinalizers();
}

port = null;

0

Isso não é relevante para a questão, mas, para as transformações XSLT no .NET (XSLCompiledTranform), talvez você não tenha escolha. Outro candidato é o controle MSHTML.



0

um bom motivo para ligar para o GC é em pequenos computadores ARM com pouca memória, como o Raspberry PI (executando com mono). Se fragmentos de memória não alocados usarem muita RAM do sistema, o sistema operacional Linux poderá ficar instável. Eu tenho um aplicativo em que preciso chamar o GC a cada segundo (!) Para se livrar dos problemas de estouro de memória.

Outra boa solução é descartar objetos quando não forem mais necessários. Infelizmente, isso não é tão fácil em muitos casos.


0

Como existem objetos pequenos (SOH) e objetos grandes (LOH)

Podemos chamar GC.Collect () para limpar o objeto de des-referência no SOP e mover o objeto ativo para a próxima geração.

No .net4.5, também podemos compactar o LOH usando largeobjectheapcompactionmode


0

Se você estiver criando muitos System.Drawing.Bitmapobjetos novos , o Garbage Collector não os limpará. Eventualmente, o GDI + pensará que você está ficando sem memória e lançará uma exceção "O parâmetro não é válido". Ligar de GC.Collect()vez em quando (não com muita frequência!) Parece resolver esse problema.

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.