Então, eu estava lendo uma pergunta sobre forçar o coletor de lixo C # a executar, onde quase todas as respostas são iguais: você pode fazê-lo, mas não deveria - exceto em casos muito raros . Infelizmente, ninguém lá elabora quais são esses casos.
Você pode me dizer em que tipo de cenário é realmente uma idéia boa ou razoável forçar a coleta de lixo?
Não estou pedindo casos específicos de C #, mas todas as linguagens de programação que possuem um coletor de lixo. Eu sei que você não pode forçar o GC em todas as linguagens, como Java, mas vamos supor que sim.
Respostas:
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 fazMain()
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 com
GC.SuppressFinalize
o que é comum emDispose()
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 naFReachable
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ãoGC.SuppressFinalize
serão coletados na geração 2.fonte
Vou dar alguns exemplos. Em suma, é raro forçar um GC é uma boa ideia, mas pode valer totalmente a pena. Esta resposta é da minha experiência com a literatura .NET e GC. Deve generalizar bem para outras plataformas (pelo menos aquelas que possuem um GC significativo).
Se seu objetivo for a taxa de transferência, quanto mais raro o GC, melhor. Nesses casos, forçar uma coleção não pode ter um impacto positivo (exceto problemas bastante planejados, como aumentar a utilização do cache da CPU, removendo objetos mortos intercalados nos ativos). A coleta de lotes é mais eficiente para todos os colecionadores que eu conheço. Para aplicativos de produção com consumo de memória em estado estacionário, induzir um GC não ajuda.
Os exemplos dados acima visam a consistência e a limitação do uso de memória. Nesses casos, os GCs induzidos podem fazer sentido.
Parece haver uma ideia ampla de que o CG é uma entidade divina que induz uma coleção sempre que é realmente ideal fazê-lo. Nenhum GC que eu conheço é tão sofisticado e, de fato, é muito difícil ser ideal para o GC. O GC sabe menos do que o desenvolvedor. Suas heurísticas são baseadas em contadores de memória e coisas como taxa de coleta e assim por diante. As heurísticas geralmente são boas, mas não capturam mudanças repentinas no comportamento do aplicativo, como liberação de grandes quantidades de memória gerenciada. Também é cego para recursos não gerenciados e para requisitos de latência.
Observe que os custos do GC variam com o tamanho da pilha e o número de referências na pilha. Em uma pequena pilha, o custo pode ser muito pequeno. Vi taxas de coleta do G2 com o .NET 4.5 de 1-2 GB / s em um aplicativo de produção com tamanho de heap de 1 GB.
fonte
Como princípio geral, um coletor de lixo é coletado quando se depara com "pressão de memória", e é uma boa idéia não coletá-lo em outros momentos porque você pode causar problemas de desempenho ou até pausas perceptíveis na execução do programa. E, de fato, o primeiro ponto depende do segundo: para um coletor de lixo geracional, pelo menos, ele roda com mais eficiência, quanto maior a proporção de lixo para bons objetos, de modo a minimizar a quantidade de tempo gasto na pausa do programa , tem que procrastinar e deixar o lixo se acumular o máximo possível.
O momento apropriado para chamar manualmente o coletor de lixo é quando você termina de fazer algo que 1) provavelmente criou muito lixo e 2) espera que o usuário demore algum tempo e deixe o sistema sem resposta de qualquer forma. Um exemplo clássico é no final do carregamento de algo grande (um documento, um modelo, um novo nível etc.)
fonte
Uma coisa que ninguém mencionou é que, enquanto o GC do Windows é incrivelmente bom, o GC no Xbox é lixo (trocadilhos) .
Portanto, ao codificar um jogo XNA para rodar no XBox, é absolutamente crucial cronometrar a coleta de lixo para momentos oportunos, ou você terá horríveis soluços intermitentes no FPS. Além disso, no XBox, é comum usar o
struct
s way, com mais frequência do que você normalmente faria, para minimizar o número de objetos que precisam ser coletados no lixo.fonte
A coleta de lixo é, acima de tudo, uma ferramenta de gerenciamento de memória. Como tal, os coletores de lixo serão coletados quando houver pressão de memória.
Os coletores de lixo modernos são muito bons e estão melhorando; portanto, é improvável que você possa aprimorá-los coletando manualmente. Mesmo que você possa melhorar as coisas hoje, pode ser que uma melhoria futura no seu coletor de lixo escolhido torne sua otimização ineficaz ou até contraproducente.
No entanto , os coletores de lixo normalmente não tentam otimizar o uso de recursos que não sejam a memória. Em ambientes de coleta de lixo, os recursos mais valiosos que não são de memória têm um
close
método ou similar, mas há algumas ocasiões em que esse não é o caso por algum motivo, como compatibilidade com uma API existente.Nesses casos, pode fazer sentido chamar manualmente a coleta de lixo quando você souber que um recurso valioso que não está em memória está sendo usado.
RMI
Um exemplo concreto disso é a Remote Method Invocation do Java. RMI é uma biblioteca de chamadas de procedimento remoto. Você normalmente tem um servidor, que disponibiliza vários objetos para uso dos clientes. Se um servidor souber que um objeto não está sendo usado por nenhum cliente, esse objeto estará qualificado para a coleta de lixo.
No entanto, a única maneira que o servidor sabe disso é se o cliente informar, e o cliente informará ao servidor que não precisa mais de um objeto depois que o cliente tiver coletado o lixo independentemente do que estiver usando.
Isso apresenta um problema, pois o cliente pode ter muita memória livre e, portanto, pode não executar a coleta de lixo com muita frequência. Enquanto isso, o servidor pode ter muitos objetos não utilizados na memória, que não podem ser coletados porque não sabem que o cliente não os está usando.
A solução no RMI é que o cliente execute a coleta de lixo periodicamente, mesmo quando houver muita memória livre, para garantir que os objetos sejam coletados imediatamente no servidor.
fonte
using
bloco ou chamar umClose
método para garantir que o recurso seja descartado o mais rápido possível. Confiar no GC para limpar recursos que não são de memória não é confiável e causa todos os tipos de problemas (principalmente nos arquivos que precisam ser bloqueados para acesso, para que possam ser abertos apenas uma vez).close
método está disponível (ou o recurso pode ser usado com umusing
bloco), essa é a abordagem correta. A resposta lida especificamente com os casos raros em que esses mecanismos não estão disponíveis.A melhor prática é não forçar uma coleta de lixo na maioria dos casos. (Todos os sistemas nos quais trabalhei que forçaram a coleta de lixo, tiveram problemas sublinhados que, se resolvidos, eliminariam a necessidade de forçar a coleta de lixo e aceleraram bastante o sistema.)
Existem alguns casos em que você sabe mais sobre o uso da memória e o coletor de lixo. É improvável que isso ocorra em um aplicativo multiusuário ou em um serviço que esteja respondendo a mais de uma solicitação por vez.
No entanto, em algum processamento de tipo de lote, você conhece mais do que o GC. Por exemplo, considere uma aplicação que.
Você poderá realizar um teste (após cuidadoso) de que você deve forçar uma coleta de lixo completa depois de processar cada arquivo.
Outros casos é um serviço que acorda a cada poucos minutos para processar alguns itens e não mantém nenhum estado enquanto dorme . Forçar uma coleção completa antes de dormir pode valer a pena.
Eu preferiria ter uma API de coleta de lixo quando pudesse dar dicas sobre esse tipo de coisa sem ter que forçar um GC sozinho.
Veja também " Boatos sobre o desempenho de Rico Mariani "
fonte
Existem vários casos em que você pode querer chamar gc ().
gc()
chamada, poucos objetos permaneçam e muito menos sejam movidos para o espaço da geração anterior ] Quando você criar uma grande coleção de objetos e usar muita memória. Você simplesmente deseja limpar o máximo de espaço possível. Isso é apenas senso comum. Ao chamargc()
manualmente, não haverá verificação redundante do gráfico de referência em parte dessa grande coleção de objetos que você está carregando na memória. Em resumo, se você executargc()
antes de carregar muito na memória, ogc()
induzido durante a carga acontece menos em pelo menos uma vez quando o carregamento começa a criar pressão de memória.grandeobjetos e é improvável que você carregue mais objetos na memória. Em resumo, você passa da fase de criação para a fase de uso. Ao chamar,gc()
dependendo da implementação, a memória usada será compactada, o que melhora enormemente a localidade do cache. Isso resultará em grande melhoria no desempenho que você não obterá da criação de perfil .gc()
e a implementação do gerenciamento de memória suportar, você criará uma continuidade muito melhor para sua memória física. Isso novamente torna a nova grande coleção de objetos mais contínua e compacta, o que, por sua vez, melhora o desempenhofonte
By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling.
Se você alocar uma tonelada de objetos em uma linha, as probabilidades já estão compactadas. Se houver, a coleta de lixo pode embaralhá-los um pouco. De qualquer maneira, o uso de estruturas de dados densas e que não pulam aleatoriamente na memória terá um impacto maior. Se você estiver usando uma lista vinculada de um elemento por nó ingênua, nenhuma quantidade de truques manuais do GC compensará isso.Um exemplo do mundo real:
Eu tinha um aplicativo Web que fazia uso de um conjunto muito grande de dados que raramente mudavam e que precisavam ser acessados muito rapidamente (rápido o suficiente para resposta por pressionamento de tecla via AJAX).
O óbvio o suficiente para fazer aqui é carregar o gráfico relevante na memória e acessá-lo a partir daí, e não no banco de dados, atualizando o gráfico quando o banco de dados é alterado.
Mas, sendo muito grande, uma carga ingênua ocuparia pelo menos 6 GB de memória com os dados, devido ao crescimento no futuro. (Eu não tenho números exatos, uma vez que ficou claro que minha máquina de 2 GB estava tentando lidar com pelo menos 6 GB, eu tinha todas as medidas necessárias para saber que não iria funcionar).
Felizmente, havia um grande número de objetos imutáveis em picolé nesse conjunto de dados que eram iguais entre si; depois que eu descobrisse que um determinado lote era o mesmo que outro lote, eu poderia usar uma referência ao outro, permitindo que muitos dados fossem coletados e, portanto, encaixando tudo em menos de meio show.
Tudo bem, mas para isso ainda agitamos mais de 6 GB de objetos no espaço de meio minuto para chegar a esse estado. Deixada por conta própria, a GC não aguentou; o pico de atividade em relação ao padrão usual do aplicativo (muito menos pesado em desalocações por segundo) foi muito acentuado.
Portanto, chamar periodicamente
GC.Collect()
durante esse processo de compilação significava que tudo funcionava sem problemas. Obviamente, eu não liguei manualmenteGC.Collect()
o resto do tempo em que o aplicativo é executado.Este caso do mundo real é um bom exemplo das diretrizes de quando devemos usar
GC.Collect()
:Na maioria das vezes, quando pensei que poderia ter um caso em que
GC.Collect()
valha a pena chamar, porque os pontos 1 e 2 se aplicavam, o ponto 3 sugeria que piorava as coisas ou pelo menos não melhorava (e com pouca ou nenhuma melhoria eu incline-se para não chamar a chamada, pois a abordagem tem mais chances de ser melhor durante a vida útil de um aplicativo).fonte
Eu tenho um uso para a eliminação de lixo que é um pouco heterodoxo.
Existe essa prática equivocada que infelizmente é muito prevalente no mundo C #, de implementar o descarte de objetos usando o idioma feio, desajeitado, deselegante e suscetível a erros conhecido como descarte descartável . O MSDN a descreve detalhadamente , e muitas pessoas juram, seguem religiosamente, passam horas e horas discutindo precisamente como isso deve ser feito etc.
(Observe que o que estou chamando de feio aqui não é o padrão de descarte de objetos; o que estou chamando de feio é o
IDisposable.Dispose( bool disposing )
idioma específico .)Esse idioma foi inventado porque é supostamente impossível garantir que o destruidor de seus objetos sempre seja invocado pelo coletor de lixo para limpar os recursos, para que as pessoas realizem a limpeza de recursos por dentro
IDisposable.Dispose()
e, caso se esqueçam, também tentam mais uma vez. dentro do destruidor. Você sabe, apenas por precaução.Mas então você
IDisposable.Dispose()
pode ter objetos gerenciados e não gerenciados para limpar, mas os objetos gerenciados não podem ser limpos quandoIDisposable.Dispose()
são invocados de dentro do destruidor, porque eles já foram tratados pelo coletor de lixo naquele momento. é essa a necessidade de umDispose()
método separado que aceite umbool disposing
sinalizador para saber se os objetos gerenciados e não gerenciados devem ser limpos ou apenas os não gerenciados.Com licença, mas isso é insano.
Eu passo pelo axioma de Einstein, que diz que as coisas devem ser o mais simples possível, mas não mais simples. Claramente, não podemos omitir a limpeza de recursos; portanto, a solução mais simples possível deve incluir pelo menos isso. A próxima solução mais simples envolve sempre descartar tudo no momento exato em que ele deve ser descartado, sem complicar as coisas, contando com o destruidor como alternativa.
Agora, estritamente falando, é obviamente impossível garantir que nenhum programador cometa o erro de esquecer de invocar
IDisposable.Dispose()
, mas o que podemos fazer é usar o destruidor para detectar esse erro. Na verdade, é muito simples: tudo o que o destruidor precisa fazer é gerar uma entrada de log se detectar que odisposed
sinalizador do objeto descartável nunca foi definidotrue
. Portanto, o uso do destruidor não é parte integrante de nossa estratégia de descarte, mas é nosso mecanismo de garantia de qualidade. E como esse é um teste apenas no modo de depuração, podemos colocar todo o destruidor dentro de um#if DEBUG
bloco, para nunca incorrer em nenhuma penalidade de destruição em um ambiente de produção. (OIDisposable.Dispose( bool disposing )
idioma prescreve queGC.SuppressFinalize()
deve ser invocado precisamente para diminuir a sobrecarga da finalização, mas com o meu mecanismo é possível evitar completamente essa sobrecarga no ambiente de produção.)O que se resume é o eterno erro rígido vs. argumento de erro leve : o
IDisposable.Dispose( bool disposing )
idioma é uma abordagem de erro suave e representa uma tentativa de permitir que o programador esqueça de invocarDispose()
sem que o sistema falhe, se possível. A abordagem de erro rígido diz que o programador deve sempre garantir que issoDispose()
será invocado. A penalidade geralmente prescrita pela abordagem de erro grave na maioria dos casos é falha de asserção, mas nesse caso específico, fazemos uma exceção e diminuímos a penalidade para uma simples emissão de uma entrada de log de erro.Portanto, para que esse mecanismo funcione, a versão DEBUG do nosso aplicativo deve executar um descarte de lixo completo antes de sair, para garantir que todos os destruidores serão chamados e, assim, capturar quaisquer
IDisposable
objetos que esquecemos de descartar.fonte
Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose()
Na verdade, não é, embora eu não ache que o C # seja capaz disso. Não exponha o recurso; em vez disso, forneça uma DSL para descrever tudo o que você fará com ela (basicamente uma mônada), além de uma função que adquira o recurso, faça as coisas, libere e retorne o resultado. O truque é usar o sistema de tipos para garantir que, se alguém contrabandear uma referência para o recurso, ele não possa ser usado em outra chamada para a função de execução.Dispose(bool disposing)
(que não está definidoIDisposable
é que ele é usado para lidar com a limpeza de objetos gerenciados e não gerenciados) que o objeto possui como um campo (ou é responsável por), que está resolvendo o problema errado. objetos não gerenciados em um objeto gerenciado sem outros objetos descartáveis com os quais se preocupar, todos osDispose()
métodos serão um deles (faça com que o finalizador faça a mesma limpeza, se necessário) ou apenas com objetos gerenciados para serem descartados (não tenha um finalizador em tudo), ea necessidade debool disposing
desaparecer.dispose(disposing)
idioma ser terribad, mas digo isso porque as pessoas costumam usar essa técnica e finalizadores quando eles só têm recursos gerenciados (oDbConnection
objeto, por exemplo, é gerenciado , não é pontilhado ou com empacotamento), e VOCÊ DEVE SOMENTE NUNCA IMPLEMENTA UM FINALIZADOR COM CÓDIGO NÃO GERENCIADO, PINVOKED, COM MARSHALLED OU NÃO SEGURO . Eu detalhei acima em minha resposta como os finalizadores são extremamente caros, não os use a menos que você tenha recursos não gerenciados em sua classe.dispose(dispoing)
idioma, mas a verdade é que isso é tão predominante porque as pessoas têm tanto medo de coisas do GC que algo tão não relacionado como isso (dispose
deveria ter nada a ver com GC) merece que eles tomem apenas o medicamento prescrito sem sequer investigá-lo. Bom para você para verificá-lo, mas você perdeu o maior inteiro (que incentiva finalizadores FARRR mais frequentemente do que deveria ser)Falando muito teoricamente e desconsiderando questões como algumas implementações de GC que diminuem a velocidade durante seus ciclos de coleta, o maior cenário em que consigo pensar em forçar a coleta de lixo é um software de missão crítica, onde os vazamentos lógicos são preferíveis aos travamentos de ponteiros pendurados, por exemplo, porque travam em momentos inesperados pode custar vidas humanas ou algo desse tipo.
Se você olhar para alguns dos jogos indie mais ruins escritos usando linguagens da GC, como jogos em Flash, eles vazam como loucos, mas não travam. Eles podem levar dez vezes a memória em 20 minutos para jogar o jogo, porque parte da base de código do jogo esqueceu de definir uma referência como nula ou removê-la de uma lista, e as taxas de quadros podem começar a sofrer, mas o jogo ainda funciona. Um jogo semelhante escrito com códigos de má qualidade C ou C ++ pode falhar como resultado do acesso a ponteiros pendentes como resultado do mesmo tipo de erro de gerenciamento de recursos, mas não vazaria tanto.
Para jogos, a falha pode ser preferível no sentido de que pode ser rapidamente detectada e corrigida, mas para um programa de missão crítica, falhas em momentos totalmente inesperados podem matar alguém. Portanto, os principais casos que acho que seriam cenários em que não há falhas ou outras formas de segurança são absolutamente críticos, e um vazamento lógico é uma coisa relativamente trivial em comparação.
O cenário principal em que acho ruim forçar o GC é para coisas em que o vazamento lógico é realmente menos preferível do que uma falha. Nos jogos, por exemplo, a falha não mata necessariamente ninguém e pode ser facilmente capturada e corrigida durante os testes internos, enquanto um vazamento lógico pode passar despercebido mesmo depois que o produto é lançado, a menos que seja tão grave que torne o jogo impossível de ser jogado em questão de minutos. . Em alguns domínios, uma falha facilmente reproduzível que ocorre nos testes às vezes é preferível a um vazamento que ninguém nota imediatamente.
Outro caso em que posso pensar em que seria preferível forçar o GC a uma equipe é para um programa de vida muito curta, como apenas algo executado na linha de comando que executa uma tarefa e depois é encerrado. Nesse caso, a vida útil do programa é muito curta para tornar qualquer tipo de vazamento lógico não trivial. Vazamentos lógicos, mesmo para grandes recursos, geralmente só se tornam problemáticos horas ou minutos após a execução do software; portanto, é improvável que um software que seja executado apenas por 3 segundos tenha problemas com vazamentos lógicos, o que poderia causar muitas coisas. É mais fácil escrever programas de curta duração se a equipe acabou de usar o GC.
fonte