Você realmente não pode fazer declarações gerais sobre a maneira apropriada de usar todas as implementações de GC. Eles variam muito. Então, falarei com o .NET ao qual você se referiu originalmente.
Você deve conhecer o comportamento do GC intimamente para fazer isso com qualquer lógica ou razão.
O único conselho sobre coleção que posso dar é: nunca faça isso.
Se você realmente conhece os intrincados detalhes do GC, não precisará de meus conselhos para que isso não importe. Se você ainda não sabe com 100% de confiança, isso ajudará e terá que procurar on-line e encontrar uma resposta como esta: Você não deve ligar para o GC.Collect ou, alternativamente: você deve aprender os detalhes de como o GC funciona por dentro e por fora, e só então você saberá a resposta .
Há um lugar seguro que faz algum sentido usar o GC .
GC.Collect é uma API disponível que você pode usar para determinar o tempo das coisas. Você poderia criar um perfil de um algoritmo, coletar e criar outro algoritmo imediatamente depois, sabendo que o GC do primeiro algo não estava ocorrendo durante o segundo distorcendo os resultados.
Esse tipo de criação de perfil é a única vez que eu sugeriria coletar manualmente para qualquer pessoa.
De qualquer maneira, exemplo planejado
Um caso de uso possível é que, se você carregar coisas realmente grandes, elas acabarão no Heap de Objetos Grandes, que vai direto para a Geração 2, embora novamente a Geração 2 seja para objetos de vida longa, porque são coletados com menos frequência. Se você sabe que está carregando objetos de vida curta na Geração 2 por qualquer motivo, pode limpá-los mais rapidamente para manter sua Geração 2 menor e suas coleções mais rápidas.
Este é o melhor exemplo que eu poderia apresentar, e não é bom - a pressão do LOH que você está construindo aqui causaria coleções mais frequentes, e as coleções são tão frequentes quanto são - é provável que ele esteja limpando o LOH da mesma forma que rápido como você estava soprando com objetos temporários. Simplesmente não confio em mim mesmo para presumir uma frequência de coleta melhor do que o próprio GC - sintonizado por pessoas muito mais inteligentes que eu.
Então, vamos falar sobre algumas das semânticas e mecanismos no .NET GC ... ou ..
Tudo o que acho que sei sobre o .NET GC
Por favor, quem encontrar erros aqui - me corrija. Sabe-se que grande parte do GC é magia negra e, enquanto eu tentava deixar de fora os detalhes dos quais não tinha certeza, provavelmente ainda entendi algumas coisas erradas.
Abaixo, propositalmente, faltam vários detalhes dos quais não tenho certeza, além de um conjunto de informações muito maior do que simplesmente não conheço. Use essa informação por sua conta e risco.
Conceitos de GC
O GC do .NET ocorre em momentos inconsistentes, e é por isso que é chamado de "não determinístico"; isso significa que você não pode confiar nele para ocorrer em horários específicos. Também é um coletor de lixo de gerações, o que significa que ele particiona seus objetos em quantas passagens do GC eles passaram.
Os objetos no heap da Geração 0 passaram por 0 coleções, e foram criados recentemente; nenhuma coleção ocorreu desde a instanciação. Os objetos em seu heap Gen 1 passaram por uma passagem de coleta e da mesma forma os objetos em seu heap Gen 2 passaram por duas passagens de coleção.
Agora vale a pena notar o motivo pelo qual qualifica essas gerações e partições específicas de acordo. O .NET GC reconhece apenas essas três gerações, porque as passagens de coleta que passam por esses três montões são ligeiramente diferentes. Alguns objetos podem sobreviver a passes de coleta milhares de vezes. O GC apenas os deixa do outro lado da partição de heap da Geração 2, não há sentido em particioná-los em nenhum outro lugar porque na verdade são a Geração 44; a passagem de coleta neles é igual a tudo na pilha da geração 2.
Existem propósitos semânticos para essas gerações específicas, bem como mecanismos implementados que os honram, e chegarei a eles daqui a pouco.
O que há em uma coleção
O conceito básico de uma passagem de coleta de GC é que ele verifica cada objeto em um espaço de heap para ver se ainda existem referências ativas (raízes do GC) para esses objetos. Se uma raiz de GC for encontrada para um objeto, isso significa que atualmente o código em execução ainda pode alcançar e usar esse objeto e, portanto, não pode ser excluído. No entanto, se uma raiz do GC não for encontrada para um objeto, isso significa que o processo em execução não precisa mais do objeto, portanto, ele pode ser removido para liberar memória para novos objetos.
Agora, depois de terminar de limpar um monte de objetos e deixar alguns em paz, haverá um efeito colateral lamentável: lacunas de espaço livre entre objetos vivos, onde os mortos foram removidos. Essa fragmentação de memória, se deixada sozinha, simplesmente desperdiçaria memória; portanto, as coleções normalmente fazem o que é chamado de "compactação", onde pegam todos os objetos ativos restantes e os juntam no heap para que a memória livre seja contígua em um lado do heap para Gen 0
Agora, dada a idéia de três montes de memória, todos particionados pelo número de passagens de coleção pelas quais eles passaram, vamos falar sobre por que essas partições existem.
Coleção Gen 0
A geração 0, que é o mais novo objeto absoluto, tende a ser muito pequena - para que você possa coletá-lo com segurança com muita frequência . A frequência garante que o heap permaneça pequeno e que as coleções sejam muito rápidas porque estão sendo coletadas sobre um heap tão pequeno. Isso se baseia mais ou menos em uma heurística que afirma: A grande maioria dos objetos temporários criados por você é muito temporária; portanto, temporários, eles não serão mais usados ou referenciados quase imediatamente após o uso e, portanto, podem ser coletados.
Coleção Gen 1
Sendo a geração 1, objetos que não se enquadram nessa categoria muito temporária de objetos, ainda podem ter vida curta, porque ainda assim - uma grande parte dos objetos criados não é usada por muito tempo. Portanto, a geração 1 também coleta com bastante frequência, mantendo novamente a pilha pequena, para que as coleções sejam rápidas. No entanto, a suposição é menor de que seus objetos são temporários que a geração 0, portanto, ela é coletada com menos frequência que a geração 0
Eu direi que, francamente, não conheço os mecanismos técnicos que diferem entre o passe de coleta do Gen 0 e o do Gen 1, se houver algum outro além da frequência que eles coletam.
Coleção Gen 2
A geração 2 agora deve ser a mãe de todos os montões, certo? Bem, sim, isso é mais ou menos certo. É onde todos os seus objetos permanentes vivem - o objeto em que você Main()
vive, por exemplo, e tudo o que faz Main()
referência, porque esses serão enraizados até que você Main()
retorne ao final do processo.
Dado que a geração 2 é um balde para basicamente tudo o que as outras gerações não conseguiram coletar, seus objetos são em grande parte permanentes ou duram no mínimo. Portanto, reconhecer muito pouco do que está na geração 2 será algo que pode ser coletado, não sendo necessário coletá-lo com frequência. Isso permite que sua coleção também seja mais lenta, pois é executada com muito menos frequência. Então é basicamente aqui que eles adotam todos os comportamentos extras para cenários estranhos, porque eles têm tempo para executá-los.
Pilha de Objetos Grandes
Um exemplo dos comportamentos extras da geração 2 é que ele também faz a coleção no heap de objetos grandes. Até agora, eu tenho falado inteiramente sobre o Small Object Heap, mas o tempo de execução do .NET aloca coisas de determinados tamanhos para um heap separado por causa do que me referi como compactação acima. A compactação requer mover objetos quando as coleções terminam no Small Object Heap. Se houver um objeto vivo de 10mb na geração 1, levará muito mais tempo para concluir a compactação após a coleta, tornando a coleção da geração 1 mais lenta. Para que o objeto 10mb seja alocado para o Large Object Heap e coletado durante a geração 2, que é executada com pouca frequência.
Finalização
Outro exemplo são objetos com finalizadores. Você coloca um finalizador em um objeto que faz referência a recursos além do escopo do .NETs GC (recursos não gerenciados). O finalizador é a única maneira que o GC exige que um recurso não gerenciado seja coletado - você implementa o finalizador para fazer a coleta / remoção / liberação manual do recurso não gerenciado para garantir que ele não vaze do seu processo. Quando o GC executar a finalização de seus objetos, sua implementação limpará o recurso não gerenciado, tornando o GC capaz de remover seu objeto sem arriscar um vazamento de recursos.
O mecanismo com o qual os finalizadores fazem isso é ser referenciado diretamente em uma fila de finalização. Quando o tempo de execução aloca um objeto com um finalizador, ele adiciona um ponteiro a ele na fila de finalização e bloqueia seu objeto (chamado de fixação) para que a compactação não o mova, o que quebraria a referência da fila de finalização. À medida que as passagens de coleta ocorrem, eventualmente, seu objeto não possui mais uma raiz de GC, mas a finalização deve ser executada antes de poder ser coletada. Portanto, quando o objeto estiver morto, a coleção moverá sua referência da fila de finalização e fará uma referência a ela no que é conhecido como fila "FReachable". Então a coleção continua. Em outro momento "não determinístico" no futuro, um thread separado conhecido como thread do Finalizador passará pela fila FReachable, executando os finalizadores para cada um dos objetos mencionados. Após terminar, a fila FReachable fica vazia e virou um pouco no cabeçalho de cada objeto que diz que não precisa de finalização (esse bit também pode ser invertido manualmente comGC.SuppressFinalize
o que é comum em Dispose()
métodos), eu também suspeito que ele tenha desafixado os objetos, mas não me cite. A próxima coleção que aparecer em qualquer pilha deste objeto, finalmente a coletará. As coleções da geração 0 nem prestam atenção aos objetos com esse bit necessário para finalização, mas os promovem automaticamente, sem mesmo verificar sua raiz. Um objeto não enraizado que precise de finalização na geração 1 será lançado na FReachable
fila, mas a coleção não fará mais nada com ela; portanto, ela entrará na geração 2. Dessa forma, todos os objetos que têm um finalizador e não GC.SuppressFinalize
serão coletados na geração 2.