Basicamente, aprendi até agora que a coleta de lixo apaga para sempre qualquer estrutura de dados que não está sendo apontada no momento. Mas isso apenas verifica a pilha para tais condições.
Por que também não verifica a seção de dados (globais, constantes, etc etc) ou a pilha também? O que há na pilha que é a única coisa que queremos que seja coletada no lixo?
data
garbage-collection
Templário das Trevas
fonte
fonte
Respostas:
O coletor de lixo faz a varredura da pilha - para ver o que as coisas na pilha estão sendo usados atualmente (apontado) por coisas na pilha.
Não faz sentido que o coletor de lixo considere coletar memória da pilha porque a pilha não é gerenciada dessa maneira: tudo na pilha é considerado "em uso". E a memória usada pela pilha é recuperada automaticamente quando você retorna de chamadas de método. O gerenciamento de memória do espaço da pilha é tão simples, barato e fácil que você não gostaria que a coleta de lixo estivesse envolvida.
(Existem sistemas, como smalltalk, em que os quadros de pilha são objetos de primeira classe armazenados na pilha e no lixo coletados como todos os outros objetos. Mas essa não é a abordagem popular atualmente. A JVM do Java e o CLR da Microsoft usam a pilha de hardware e a memória contígua .)
fonte
Vire sua pergunta. A verdadeira questão motivadora está em que circunstâncias podemos evitar os custos da coleta de lixo?
Bem, primeiro, quais são os custos da coleta de lixo? Existem dois custos principais. Primeiro, você precisa determinar o que está vivo ; isso requer potencialmente muito trabalho. Segundo, você precisa compactar os furos formados quando liberar algo que foi alocado entre duas coisas que ainda estão vivas. Esses buracos são um desperdício. Mas compactá-los também é caro.
Como podemos evitar esses custos?
Claramente, se você puder encontrar um padrão de uso de armazenamento no qual nunca aloque algo de longa duração, aloque algo de curta duração e aloque algo de longa duração, poderá eliminar o custo dos furos. Se você pode garantir que, para algum subconjunto de seu armazenamento, cada alocação subsequente tenha uma vida útil mais curta que a anterior nesse armazenamento, nunca haverá buracos nesse armazenamento.
Mas se resolvemos o problema do furo , também resolvemos o problema da coleta de lixo . Você tem algo nesse armazenamento que ainda está vivo? Sim. Tudo foi alocado antes de durar mais? Sim - essa suposição é como eliminamos a possibilidade de buracos. Portanto, tudo o que você precisa fazer é dizer "a alocação mais recente está viva?" e você sabe que tudo está vivo nesse armazenamento.
Temos um conjunto de alocações de armazenamento em que sabemos que todas as alocações subseqüentes têm vida útil mais curta que a alocação anterior? Sim! Os quadros de ativação dos métodos são sempre destruídos na ordem oposta à qual foram criados, porque eles sempre têm vida mais curta do que a ativação que os criou.
Portanto, podemos armazenar quadros de ativação na pilha e saber que eles nunca precisam ser coletados. Se houver algum quadro na pilha, o conjunto inteiro de quadros abaixo terá vida mais longa, portanto, eles não precisam ser coletados. E eles serão destruídos na ordem oposta em que foram criados. O custo da coleta de lixo é eliminado para os quadros de ativação.
É por isso que temos o pool temporário na pilha em primeiro lugar: porque é uma maneira fácil de implementar a ativação do método sem incorrer em uma penalidade no gerenciamento de memória.
(É claro que o custo da coleta de lixo da memória referida pelas referências nos quadros de ativação ainda existe.)
Agora considere um sistema de fluxo de controle no qual os quadros de ativação não sejam destruídos em uma ordem previsível. O que acontece se uma ativação de curta duração pode dar origem a uma ativação de longa duração? Como você pode imaginar, neste mundo você não pode mais usar a pilha para otimizar a necessidade de coletar ativações. O conjunto de ativações pode conter furos novamente.
O C # 2.0 possui esse recurso na forma de
yield return
. Um método que produz um retorno de rendimento será reativado posteriormente - na próxima vez que MoveNext for chamado - e quando isso acontecer não será previsível. Portanto, as informações que normalmente estariam na pilha para o quadro de ativação do bloco iterador são armazenadas na pilha, onde são coletadas como lixo quando o enumerador é coletado.Da mesma forma, o recurso "async / waitit", que vem nas próximas versões do C # e do VB, permitirá criar métodos cujas ativações "produzem" e "resumem" em pontos bem definidos durante a ação do método. Como os quadros de ativação não são mais criados e destruídos de maneira previsível, todas as informações que costumavam ser armazenadas na pilha precisam ser armazenadas no heap.
Foi apenas um acidente da história que decidimos por algumas décadas que os idiomas com quadros de ativação criados e destruídos de maneira estritamente ordenada estavam na moda. Como os idiomas modernos carecem cada vez mais dessa propriedade, espere ver mais e mais idiomas que refificam as continuações no heap de coleta de lixo, em vez da pilha.
fonte
A resposta mais óbvia, e talvez não a mais completa, é que o heap é o local dos dados da instância. Por dados de instância, entendemos os dados que representam as instâncias de classes, também conhecidas como objetos, criadas em tempo de execução. Esses dados são inerentemente dinâmicos e o número desses objetos e, portanto, a quantidade de memória que eles ocupam, são conhecidos apenas em tempo de execução. É necessário que haja alguma recuperação da memória ou programas de longa duração consumiriam toda a memória ao longo do tempo.
A memória consumida por definições de classe, constantes e outras estruturas de dados estáticas é inerentemente improvável que aumente desmarcada. Como há apenas uma definição de classe na memória por um número desconhecido de instâncias de tempo de execução dessa classe, faz sentido que esse tipo de estrutura não seja uma ameaça ao uso da memória.
fonte
Vale lembrar que temos a coleta de lixo: porque às vezes é difícil saber quando desalocar a memória. Você realmente só tem esse problema com a pilha. Os dados alocados na pilha serão desalocados eventualmente, portanto, não há realmente nenhuma necessidade de fazer coleta de lixo lá. Presume-se que as coisas na seção de dados estejam alocadas durante a vida útil do programa.
fonte
O tamanho desses é previsível (constante, exceto para a pilha, e a pilha geralmente é limitada a alguns MB) e geralmente muito pequena (pelo menos em comparação com as centenas de MB que aplicativos grandes podem alocar).
Objetos alocados dinamicamente geralmente têm um pequeno período de tempo em que são alcançáveis. Depois disso, não há como eles serem referenciados novamente. Compare isso com as entradas na seção de dados, variáveis globais e outras coisas: freqüentemente, há um pedaço de código que as referencia diretamente (pense
const char *foo() { return "foo"; }
). Normalmente, o código não muda, portanto, a referência existe para permanecer e outra referência será criada sempre que a função for chamada (que pode ser a qualquer momento, tanto quanto o computador sabe - a menos que você resolva o problema de interrupção, ou seja, ) Portanto, você não poderia liberar a maior parte dessa memória, pois ela sempre seria acessível.Em muitas linguagens de coleta de lixo, tudo o que pertence ao programa sendo executado é alocado para heap. No Python, simplesmente não há nenhuma seção de dados nem valores alocados à pilha (existem as referências de variáveis locais e a pilha de chamadas, mas também não há um valor no mesmo sentido que
int
em C). Todo objeto está na pilha.fonte
Como vários outros respondentes disseram, a pilha faz parte do conjunto raiz, portanto é varrida por referências, mas não "coletada", por si só.
Eu só quero responder a alguns dos comentários que implicam que o lixo na pilha não importa; isso ocorre, pois pode fazer com que mais lixo no heap seja considerado acessível. Gravadores conscientes de VM e compilador anulam ou excluem partes mortas da pilha da varredura. No IIRC, algumas VMs têm tabelas de mapeamento de intervalos de PCs para bitmaps de pilha dinâmica e outras apenas anulam os slots. Não sei qual técnica é atualmente preferida.
Um termo usado para descrever essa consideração em particular é seguro para o espaço .
fonte
Deixe-me apontar alguns conceitos errôneos fundamentais que você e muitos outros erraram:
"Por que a coleta de lixo apenas varre a pilha?" É o contrário. Somente os coletores de lixo mais simples, mais conservadores e mais lentos varrem a pilha. É por isso que eles são tão lentos.
Os coletores de lixo rápidos varrem apenas a pilha (e, opcionalmente, algumas outras raízes, como algumas globais para ponteiros FFI e os registros para ponteiros ativos), e apenas copiam os ponteiros alcançáveis pelos objetos da pilha. O restante é jogado fora (ou seja, ignorado), sem varrer a pilha.
Como o heap é cerca de 1000x maior que a (s) pilha (s), esse GC de varredura de pilha geralmente é muito mais rápido. ~ 15ms vs 250ms em montões de tamanho normal. Como está copiando (movendo) os objetos de um espaço para outro, é chamado principalmente de coletor de cópias semi-espaciais, ele precisa de 2 vezes de memória e, portanto, geralmente não é utilizável em dispositivos muito pequenos, como telefones com pouca memória. É compacto e, portanto, muito mais amigável para o cache, ao contrário dos simples scanners de pilha e varredura.
Como está movendo ponteiros, FFI, identidade e referências são complicadas. A identidade geralmente é resolvida com IDs aleatórios, referências através de ponteiros de encaminhamento. A FFI é complicada, pois objetos estranhos não podem conter ponteiros para o espaço antigo. Os ponteiros de FFI são geralmente mantidos em uma arena separada, por exemplo, com um coletor estático de marcações e varreduras lentas. Ou malloc trivial com recontagem. Observe que o malloc tem uma enorme sobrecarga e conta ainda mais.
Marcar e varrer é trivial de implementar, mas não deve ser usado em programas reais e, principalmente, não deve ser ensinado como o coletor padrão. O mais famoso desses coletores de cópias de escaneamento rápido é chamado coletor de dois dedos Cheney .
fonte
O que é alocado na pilha? Variáveis locais e endereços de retorno (em C). Quando uma função retorna, suas variáveis locais são descartadas. Não é necessário nem prejudicial varrer a pilha.
Muitas linguagens dinâmicas, e também Java ou C # são implementadas em uma linguagem de programação de sistema, geralmente em C. Você poderia dizer que Java é implementado com funções C e usa variáveis locais C e, portanto, o coletor de lixo de Java não precisa varrer a pilha.
Há uma exceção interessante: o coletor de lixo do Chicken Scheme varre a pilha (de certa forma), porque sua implementação usa a pilha como um espaço de primeira geração da coleta de lixo: consulte Wikipedia .
fonte