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.