Em poucas palavras
A finalização não é uma questão simples de ser tratada pelos coletores de lixo. É fácil de usar com o GC de contagem de referência, mas essa família de GC geralmente é incompleta, exigindo que as perdas de memória sejam compensadas pelo acionamento explícito da destruição e finalização de alguns objetos e estruturas. O rastreamento de coletores de lixo é muito mais eficaz, mas torna muito mais difícil identificar o objeto a ser finalizado e destruído, em vez de apenas identificar a memória não utilizada, exigindo, portanto, um gerenciamento mais complexo, com um custo no tempo e no espaço e na complexidade de a implementação.
Introdução
Suponho que o que você está perguntando é por que as linguagens de coleta de lixo não tratam automaticamente a destruição / finalização no processo de coleta de lixo, conforme indicado pela observação:
Acho extremamente carente que essas linguagens considerem a memória como o único recurso que vale a pena gerenciar. E quanto a soquetes, identificadores de arquivo, estados de aplicativos?
Não concordo com a resposta aceita dada por kdbanman . Embora os fatos declarados sejam na maioria corretos, embora fortemente tendenciosos à contagem de referências, não creio que eles expliquem adequadamente a situação reclamada na pergunta.
Não acredito que a terminologia desenvolvida nessa resposta seja um problema, e é mais provável que confunda as coisas. De fato, como apresentado, a terminologia é determinada principalmente pela maneira como os procedimentos são ativados, e não pelo que eles fazem. O ponto é que, em todos os casos, há a necessidade de finalizar um objeto que não é mais necessário com algum processo de limpeza e liberar quaisquer recursos que esteja utilizando, sendo a memória apenas um deles. Idealmente, tudo isso deve ser feito automaticamente quando o objeto não for mais utilizado, por meio de um coletor de lixo. Na prática, o GC pode estar ausente ou apresentar deficiências, e isso é compensado pelo disparo explícito pelo programa de finalização e recuperação.
A exploração explícita pelo programa é um problema, pois pode permitir erros de programação difíceis de analisar, quando um objeto ainda em uso está sendo explicitamente finalizado.
Portanto, é muito melhor contar com a coleta automática de lixo para recuperar recursos. Mas há dois problemas:
alguma técnica de coleta de lixo permitirá vazamentos de memória que impedem a recuperação completa de recursos. Isso é bem conhecido pela referência na contagem de GC, mas pode aparecer para outras técnicas de GC ao usar algumas organizações de dados sem cuidados (ponto não discutido aqui).
embora a técnica de GC possa ser boa para identificar recursos de memória que não são mais usados, a finalização de objetos contidos nela pode não ser simples e isso complica o problema de recuperar outros recursos usados por esses objetos, que geralmente é o objetivo da finalização.
Finalmente, um ponto importante frequentemente esquecido é que os ciclos de GC podem ser acionados por qualquer coisa, não apenas pela falta de memória, se os ganchos adequados forem fornecidos e se o custo de um ciclo de GC for considerado valioso. Portanto, não há problema em iniciar um GC quando estiver faltando algum tipo de recurso, na esperança de liberar alguns.
Referência contando coletores de lixo
A contagem de referência é uma técnica fraca de coleta de lixo , que não manipula os ciclos adequadamente. Seria, de fato, fraco na destruição de estruturas obsoletas e na recuperação de outros recursos simplesmente porque é fraco na recuperação de memória. Porém, os finalizadores podem ser usados com mais facilidade com um coletor de lixo de contagem de referência (GC), pois um GC de ref-count recupera uma estrutura quando seu número de ref desce para 0, quando seu endereço é conhecido juntamente com seu tipo, estaticamente ou dinamicamente. Portanto, é possível recuperar a memória precisamente após a aplicação do finalizador adequado e a chamada recursiva do processo em todos os objetos apontados (possivelmente através do procedimento de finalização).
Em poucas palavras, a finalização é fácil de implementar com o GC de contagem de referência, mas sofre com a "incompletude" desse GC, devido a estruturas circulares, exatamente na mesma extensão que a recuperação de memória sofre. Em outras palavras, com contagem de referência, a memória é precisamente tão mal gerenciada quanto outros recursos , como soquetes, identificadores de arquivo, etc.
De fato, a incapacidade do GC de ref count para recuperar estruturas em loop (em geral) pode ser vista como vazamento de memória . Você não pode esperar que todo o GC evite vazamentos de memória. Depende do algoritmo do GC e das informações da estrutura de tipos disponíveis dinamicamente (por exemplo, no
GC conservador ).
Rastreando coletores de lixo
A família mais poderosa do GC, sem esses vazamentos, é a família de rastreamento que explora as partes vivas da memória, começando por indicadores de raiz bem identificados. Todas as partes da memória que não são visitadas nesse processo de rastreamento (que podem realmente ser decompostas de várias maneiras, mas preciso simplificar) são partes não utilizadas da memória que podem ser recuperadas 1 . Esses coletores recuperam todas as partes da memória que não podem mais ser acessadas pelo programa, não importa o que ele faça. Ele recupera estruturas circulares, e o GC mais avançado é baseado em alguma variação desse paradigma, às vezes altamente sofisticado. Pode ser combinado com a contagem de referência em alguns casos e compensar suas fraquezas.
Um problema é que a sua declaração (no final da pergunta):
Os idiomas que oferecem coleta automática de lixo parecem ser os principais candidatos para apoiar a destruição / finalização de objetos, pois sabem com 100% de certeza quando um objeto não está mais em uso.
é tecnicamente incorreto para rastrear coletores.
O que se sabe com 100% de certeza é que partes da memória não estão mais em uso . (Mais precisamente, deve-se dizer que eles não estão mais acessíveis , porque algumas partes, que não podem mais ser usadas de acordo com a lógica do programa, ainda são consideradas em uso se ainda houver um ponteiro inútil para elas no programa .) Mas são necessários processamento adicional e estruturas apropriadas para saber quais objetos não utilizados podem ter sido armazenados nessas partes da memória que agora não são usadas . Isso não pode ser determinado pelo que é conhecido do programa, pois o programa não está mais conectado a essas partes da memória.
Assim, após um passo na coleta de lixo, você fica com fragmentos de memória que contêm objetos que não estão mais em uso, mas não há como, a priori, saber o que são esses objetos para aplicar a finalização correta. Além disso, se o coletor de rastreamento for do tipo de marcação e varredura, pode ser que alguns dos fragmentos possam conter objetos que já foram finalizados em uma passagem anterior do GC, mas não foram utilizados desde então por motivos de fragmentação. No entanto, isso pode ser resolvido usando a digitação explícita estendida.
Embora um coletor simples recupere esses fragmentos de memória, sem mais delongas, a finalização exige uma passagem específica para explorar essa memória não utilizada, identificar os objetos ali contidos e aplicar procedimentos de finalização. Mas tal exploração requer determinação do tipo de objetos que foram armazenados lá e também é necessária a determinação do tipo para aplicar a finalização adequada, se houver.
Isso implica custos extras no tempo do GC (a passagem extra) e possivelmente custos extras de memória para disponibilizar informações de tipo adequadas durante essa passagem por diversas técnicas. Esses custos podem ser significativos, pois muitas vezes é necessário finalizar apenas alguns objetos, enquanto a sobrecarga de tempo e espaço pode envolver todos os objetos.
Outro ponto é que a sobrecarga de tempo e espaço pode estar relacionada à execução do código do programa, e não apenas à execução do GC.
Não posso dar uma resposta mais precisa, apontando para questões específicas, porque não conheço as especificidades de muitos dos idiomas listados. No caso de C, a digitação é uma questão muito difícil que leva ao desenvolvimento de colecionadores conservadores. Meu palpite seria que isso também afeta C ++, mas eu não sou especialista em C ++. Isso parece ser confirmado por Hans Boehm, que fez grande parte da pesquisa sobre GC conservador. O GC conservador não pode recuperar sistematicamente toda a memória não utilizada precisamente porque pode não ter informações precisas sobre o tipo de dados. Pelo mesmo motivo, não seria possível aplicar sistematicamente os procedimentos de finalização.
Portanto, é possível fazer o que você está perguntando, como você sabe em alguns idiomas. Mas não vem de graça. Dependendo do idioma e de sua implementação, isso pode acarretar um custo, mesmo quando você não usa o recurso. Várias técnicas e trade-offs podem ser consideradas para resolver esses problemas, mas isso está além do escopo de uma resposta de tamanho razoável.
1 - esta é uma apresentação abstrata da coleção de rastreio (abrangendo o GC de copiar e marcar e varrer), as coisas variam de acordo com o tipo de coletor de rastreio, e explorar a parte não utilizada da memória é diferente, dependendo de copiar ou marcar e varredura é usada.
finalize
/destroy
é uma mentira? Não há garantia de que algum dia será executado. E, mesmo que você não saiba quando (dada a coleta automática de lixo), e se o contexto necessário ainda estiver lá (ele já pode ter sido coletado). Portanto, é mais seguro garantir um estado consistente de outras maneiras, e é possível forçar o programador a fazê-lo.