Alguns coletores de lixo (pelo menos Mono e .NET) têm uma área de memória de curto prazo que eles varrem com freqüência e uma área de memória secundária que eles varrem com menos frequência. Mono chama isso de berçário.
Para descobrir quais objetos podem ser descartados, eles examinam todos os objetos a partir de raízes, a pilha e os registros e descartam todos os objetos que não estão mais sendo referenciados.
Minha pergunta é como eles impedem que toda a memória em uso seja varrida em cada coleta? Em princípio, a única maneira de descobrir quais objetos não estão mais em uso é verificar todos os objetos e todas as suas referências. No entanto, isso impediria que o sistema operacional trocasse memória, mesmo que não esteja sendo usado pelo aplicativo e pareça uma enorme quantidade de trabalho que precisa ser feito, também para "Coleção de berçário". Não parece que eles estão ganhando muito usando um berçário.
Estou faltando alguma coisa ou o coletor de lixo está realmente varrendo todos os objetos e todas as referências toda vez que faz uma coleta?
fonte
Respostas:
As observações fundamentais que permitem que a coleta de lixo geracional evite a verificação de todos os objetos da geração mais antiga são:
Em muitas estruturas de GC, é possível para o coletor de lixo sinalizar objetos ou partes dele de tal maneira que a primeira tentativa de gravar neles acionará um código especial para registrar o fato de que eles foram modificados. Um objeto ou parte dele modificado, independentemente de sua geração, deve ser verificado na próxima coleção, pois pode conter referências a objetos mais recentes. Por outro lado, é muito comum haver muitos objetos antigos que não são modificados entre as coleções. O fato de as verificações de geração mais baixa poderem ignorar esses objetos pode permitir que essas verificações sejam concluídas muito mais rapidamente do que seriam.
Observe que, mesmo que não seja possível detectar quando os objetos são modificados e tiver que varrer tudo em cada passagem do GC, a coleta de lixo geracional ainda pode melhorar o desempenho do estágio de "varredura" de um coletor de compactação. Em alguns ambientes incorporados (especialmente aqueles em que há pouca ou nenhuma diferença de velocidade entre acessos de memória seqüenciais e aleatórios), mover blocos de memória é relativamente caro comparado às referências de marcação. Consequentemente, mesmo que a fase de "marcação" não possa ser acelerada usando um coletor de gerações, acelerar a fase de "varredura" pode valer a pena.
fonte
Os GCs aos quais você está se referindo são coletores de lixo geracionais . Eles são projetados para tirar o máximo proveito de uma observação conhecida como "mortalidade infantil" ou "hipótese geracional", o que significa que a maioria dos objetos se torna inacessível muito rapidamente. Eles realmente digitalizam a partir das raízes, mas ignoram todos os objetos antigos . Portanto, eles não precisam varrer a maioria dos objetos na memória, eles examinam apenas objetos jovens (à custa de não detectar objetos antigos inacessíveis, pelo menos não nesse ponto).
"Mas isso está errado", ouvi você gritar, "objetos antigos podem e se referem a objetos jovens". Você está certo, e existem várias soluções para isso, que giram em torno do ganho de conhecimento, rápida e eficientemente, quais objetos antigos devem ser verificados e quais são seguros para ignorar. Eles se resumem a objetos de gravação ou pequenos intervalos de memória (maiores que os objetos, mas muito menores que a pilha inteira) que contêm indicadores para as gerações mais jovens. Outros os descreveram muito melhor do que eu, portanto, darei algumas palavras-chave: Marcação de cartões, conjuntos lembrados, barreiras de gravação. Existem outras técnicas também (incluindo híbridos), mas elas abrangem as abordagens comuns que eu conheço.
fonte
Para descobrir quais objetos do viveiro ainda estão ativos, o coletor só precisa varrer o conjunto raiz e qualquer objeto antigo que tenha sido alterado desde a última coleção , pois um objeto antigo que não tenha sido alterado recentemente não poderá apontar para um objeto jovem . Existem algoritmos diferentes para manter essas informações em diferentes níveis de precisão (de um conjunto exato de campos alterados a um conjunto de páginas em que a mutação pode ter ocorrido), mas todos geralmente envolvem algum tipo de barreira de gravação : código executado em todas as referências mutação de tipo de campo que atualiza a contabilidade do GC.
fonte
A geração mais antiga e mais simples de coletores de lixo, na verdade, examinou toda a memória e teve que interromper todo o outro processamento enquanto o fazia. Algoritmos posteriores melhoraram isso de várias maneiras - tornando a cópia / digitalização incremental ou executada em paralelo. A maioria dos coletores de lixo modernos segregam objetos em gerações e gerenciam cuidadosamente ponteiros entre gerações, para que as gerações mais novas possam ser coletadas sem perturbar as mais antigas.
O ponto principal é que os coletores de lixo trabalham em estreita colaboração com o compilador e com o restante do tempo de execução para manter a ilusão de que ele está assistindo toda a memória.
fonte
Basicamente ... O GC usa "buckets" para separar o que está em uso e o que não está. Depois de fazer a verificação, elimina as coisas que não estão em uso e move todo o resto para a 2ª geração (que é verificada com menos frequência que a 1ª geração) e depois move as coisas que ainda estão em uso na 2ª den para a 3ª geração.
Portanto, as coisas da 3ª geração geralmente são objetos que ficam presos por algum motivo e o GC não verifica lá com muita frequência.
fonte
O algoritmo normalmente usado por este GC é o Naïve mark-and-sweep
você também deve estar ciente do fato de que isso não é gerenciado pelo próprio C #, mas pelo chamado CLR .
fonte