Ou, em outras palavras, quais problemas específicos a coleta automatizada de lixo resolveu? Como nunca fiz programação de baixo nível, não sei o quão complicado pode ser a liberação de recursos.
O tipo de bugs que o GC aborda parece (pelo menos para um observador externo) o tipo de coisa que um programador que conhece bem sua linguagem, bibliotecas, conceitos, expressões idiomáticas, etc., não faria. Mas eu posso estar errado: o tratamento manual da memória é intrinsecamente complicado?
Respostas:
Engraçado como a definição de "nível baixo" muda com o tempo. Quando eu estava aprendendo a programar, qualquer idioma que fornecesse um modelo de heap padronizado que possibilitasse um padrão simples de alocação / livre foi considerado de alto nível. Na programação de baixo nível , você precisa acompanhar a memória (não as alocações, mas as próprias localizações da memória!), Ou escrever seu próprio alocador de heap, se estiver realmente interessado.
Dito isto, não há realmente nada de assustador ou "complicado". Lembra quando você era criança e sua mãe lhe disse para guardar seus brinquedos quando terminar de brincar com eles, que ela não é sua criada e não ia limpar seu quarto para você? O gerenciamento de memória é simplesmente esse mesmo princípio aplicado ao código. (A GC é como ter uma empregada que irá limpar você, mas ela é muito preguiçosa e um pouco sem noção.) O princípio é simples: cada variável no seu código tem um e apenas um proprietário, e é responsabilidade desse proprietário libere a memória da variável quando ela não for mais necessária. ( O princípio da propriedade única) Isso requer uma chamada por alocação e existem vários esquemas que automatizam a propriedade e a limpeza de uma maneira ou de outra, para que você nem precise gravar essa chamada em seu próprio código.
A coleta de lixo deve resolver dois problemas. Invariavelmente, faz um trabalho muito ruim em um deles e, dependendo da implementação, pode ou não se dar bem com o outro. Os problemas são vazamentos de memória (mantendo a memória após o término) e referências pendentes (liberando memória antes do término). Vamos examinar os dois problemas:
Referências pendentes: Discutindo este primeiro porque é realmente sério. Você tem dois ponteiros para o mesmo objeto. Você libera um deles e não percebe o outro. Em algum momento posterior, você tenta ler (ou gravar ou liberar) o segundo. Um comportamento indefinido se segue. Se você não perceber, pode facilmente corromper sua memória. A coleta de lixo deve tornar esse problema impossível, garantindo que nada seja liberado até que todas as referências a ele desapareçam. Em uma linguagem totalmente gerenciada, isso quase funciona, até que você precise lidar com recursos de memória externos não gerenciados. Depois, volto à estaca 1. E em um idioma não gerenciado, as coisas ainda são mais complicadas. (Dê uma olhada no Mozilla '
Felizmente, lidar com esse problema é basicamente um problema resolvido. Você não precisa de um coletor de lixo, precisa de um gerenciador de memória de depuração. Eu uso o Delphi, por exemplo, e com uma única biblioteca externa e uma diretiva de compilador simples, posso definir o alocador como "Modo de depuração total". Isso adiciona uma sobrecarga de desempenho insignificante (menos de 5%) em troca da ativação de alguns recursos que controlam a memória usada. Se eu liberar um objeto, ele preenche sua memória com
0x80
bytes (facilmente reconhecíveis no depurador) e se eu tentar chamar um método virtual (incluindo o destruidor) em um objeto liberado, ele notará e interromperá o programa com uma caixa de erro com três rastreamentos de pilha - quando o objeto foi criado, quando foi liberado e onde estou agora - além de outras informações úteis, gera uma exceção. Obviamente, isso não é adequado para compilações de versões, mas torna trivial o rastreamento e a correção de problemas de referência pendentes.O segundo problema são vazamentos de memória. É o que acontece quando você mantém a memória alocada quando não precisa mais dela. Isso pode acontecer em qualquer idioma, com ou sem coleta de lixo, e só pode ser corrigido escrevendo seu código corretamente. A coleta de lixo ajuda a atenuar uma forma específica de vazamento de memória, do tipo que ocorre quando você não tem referências válidas para um pedaço de memória que ainda não foi liberado, o que significa que a memória permanece alocada até o término do programa. Infelizmente, a única maneira de fazer isso de maneira automatizada é transformar cada alocação em um vazamento de memória!
Provavelmente vou ser enganado pelos defensores do GC se tentar dizer algo assim, então permita-me explicar. Lembre-se de que a definição de vazamento de memória está mantendo a memória alocada quando você não precisa mais dele. Além de não ter referências a algo, você também pode vazar memória tendo uma referência desnecessária, como mantê-la em um objeto contêiner quando deveria ter liberado. Vi alguns vazamentos de memória causados por isso e são muito difíceis de rastrear se você possui um GC ou não, pois envolvem uma referência perfeitamente válida à memória e não há "bugs" claros para as ferramentas de depuração. pegar. Até onde eu sei, não há ferramenta automatizada que permita capturar esse tipo de vazamento de memória.
Portanto, um coletor de lixo se preocupa apenas com a variedade de vazamentos de memória sem referências, porque esse é o único tipo que pode ser tratado de maneira automatizada. Se pudesse assistir todas as suas referências a tudo e liberar todos os objetos assim que não houvesse nenhuma referência a ele, seria perfeito, pelo menos no que diz respeito ao problema de não referências. Fazer isso de maneira automatizada é chamado de contagem de referência e pode ser feito em algumas situações limitadas, mas ele tem seus próprios problemas para resolver. (Por exemplo, o objeto A que mantém uma referência ao objeto B, que mantém uma referência ao objeto A. Em um esquema de contagem de referências, nenhum objeto pode ser liberado automaticamente, mesmo quando não há referências externas para A ou B.) coletores de lixo usam rastreamentoem vez disso: comece com um conjunto de objetos em bom estado, encontre todos os objetos aos quais eles fazem referência, encontre todos os objetos aos quais eles fazem referência e assim por diante recursivamente até encontrar tudo. Tudo o que não é encontrado no processo de rastreamento é lixo e pode ser jogado fora. (Isso é bem-sucedido, é claro, requer uma linguagem gerenciada que impõe certas restrições ao sistema de tipos para garantir que o coletor de lixo de rastreamento sempre possa dizer a diferença entre uma referência e uma parte aleatória da memória que parece um ponteiro.)
Existem dois problemas com o rastreamento. Primeiro, é lento e, enquanto está acontecendo, o programa precisa ser mais ou menos pausado para evitar as condições da corrida. Isso pode causar problemas de execução perceptíveis quando o programa deve estar interagindo com um usuário ou desempenho atolado em um aplicativo de servidor. Isso pode ser mitigado por várias técnicas, como dividir a memória alocada em "gerações", com o princípio de que, se uma alocação não for coletada na primeira vez que você tentar, é provável que permaneça por um tempo. Tanto a estrutura .NET quanto a JVM usam coletores de lixo geracionais.
Infelizmente, isso alimenta o segundo problema: a memória não é liberada quando você termina. A menos que o rastreio seja executado imediatamente após o término de um objeto, ele permanecerá até o próximo rastreio, ou até mais, se passar da primeira geração. De fato, uma das melhores explicações do coletor de lixo .NET que eu já vi explica que, para tornar o processo o mais rápido possível, o GC deve adiar a coleta o máximo possível! Portanto, o problema de vazamento de memória é "resolvido" de maneira bizarra, vazando o máximo de memória possível pelo maior tempo possível! É isso que quero dizer quando digo que um GC transforma toda alocação em um vazamento de memória. Na verdade, não há garantia de que qualquer objeto dado vai sempre ser recolhidos.
Por que isso é um problema, quando a memória ainda é recuperada quando necessário? Por algumas razões. Primeiro, imagine alocar um objeto grande (um bitmap, por exemplo) que consome uma quantidade significativa de memória. E logo depois que você terminar, você precisará de outro objeto grande que ocupe a mesma quantidade (ou quase a mesma) de memória. Se o primeiro objeto tivesse sido liberado, o segundo poderia reutilizar sua memória. Porém, em um sistema coletado de lixo, é possível que você ainda esteja aguardando a execução do próximo rastreamento e, portanto, acaba desperdiçando desnecessariamente memória para um segundo objeto grande. É basicamente uma condição de corrida.
Segundo, manter a memória desnecessariamente, especialmente em grandes quantidades, pode causar problemas em um moderno sistema multitarefa. Se você consome muita memória física, isso pode fazer com que seu programa ou outros programas tenham que paginar (troque parte da memória deles para o disco), o que realmente atrasa as coisas. Para certos sistemas, como servidores, a paginação pode não apenas atrasar o sistema, mas também pode travar tudo se estiver sob carga.
Como o problema de referências pendentes, o problema de não referências pode ser resolvido com um gerenciador de memória de depuração. Mais uma vez, mencionarei o Modo de depuração completo do gerenciador de memória FastMM do Delphi, já que é o que eu estou mais familiarizado. (Tenho certeza de que existem sistemas semelhantes para outros idiomas.)
Quando um programa em execução no FastMM é finalizado, você pode opcionalmente informar a existência de todas as alocações que nunca foram liberadas. O Modo de depuração completo leva um passo adiante: ele pode salvar um arquivo em disco contendo não apenas o tipo de alocação, mas um rastreamento de pilha desde quando foi alocado e outras informações de depuração, para cada alocação vazada. Isso torna o rastreamento de vazamentos de memória sem referências trivial.
Quando você realmente olha para ele, a coleta de lixo pode ou não se dar bem com a prevenção de referências pendentes e, universalmente, faz um mau trabalho no tratamento de vazamentos de memória. Sua única virtude, de fato, não é a coleta de lixo em si, mas um efeito colateral: fornece uma maneira automatizada de executar a compactação de heap. Isso pode impedir um problema misterioso (esgotamento da memória através da fragmentação de heap) que pode matar programas que são executados continuamente por um longo tempo e têm um alto grau de perda de memória, e a compactação de heap é praticamente impossível sem a coleta de lixo. No entanto, atualmente, qualquer bom alocador de memória usa buckets para minimizar a fragmentação, o que significa que a fragmentação somente se torna realmente um problema em circunstâncias extremas. Para um programa em que a fragmentação de heap provavelmente seja um problema, é ' É aconselhável usar um coletor de lixo compacto. Mas, na IMO, em qualquer outro caso, o uso da coleta de lixo é uma otimização prematura, e existem melhores soluções para os problemas que ela "resolve".
fonte
Considerando uma técnica de gerenciamento de memória sem coleta de lixo de uma época equivalente, como os coletores de lixo em uso nos sistemas populares atuais, como o RAII do C ++. Dada essa abordagem, o custo de não usar a coleta automatizada de lixo é mínimo e o GC apresenta muitos de seus próprios problemas. Como tal, sugiro que "Não muito" seja a resposta para o seu problema.
Lembre-se, quando as pessoas pensam em não-GC, elas pensam
malloc
efree
. Mas essa é uma falácia lógica gigante - você compararia o gerenciamento de recursos não pertencentes à GC do início dos anos 70 aos coletores de lixo do final dos anos 90. Esta é obviamente uma comparação bastante injusta - os coletores de lixo que estavam em uso quandomalloc
efree
foram projetados eram muito lentos para executar qualquer programa significativo, se bem me lembro. Comparar algo de um período vagamente equivalente, por exemplounique_ptr
, é muito mais significativo.Os coletores de lixo podem lidar com os ciclos de referência mais facilmente, embora sejam experiências bastante raras. Além disso, os GCs podem simplesmente "vomitar" o código, porque o GC cuidará de todo o gerenciamento de memória, o que significa que eles podem levar a ciclos de desenvolvimento mais rápidos.
Por outro lado, eles tendem a ter problemas enormes ao lidar com a memória que veio de qualquer lugar, exceto seu próprio pool de GC. Além disso, eles perdem muito de seu benefício quando a concorrência está envolvida, porque você deve considerar a propriedade do objeto de qualquer maneira.
Edit: Muitas das coisas que você menciona não têm nada a ver com o GC. Você está confundindo gerenciamento de memória e orientação a objetos. Veja, o seguinte: se você programar em um sistema não gerenciado completo, como o C ++, poderá ter a verificação de limites que desejar, e as classes de contêineres padrão o oferecem. Não há nada de GC na verificação de limites, por exemplo, ou digitação forte.
Os problemas mencionados são resolvidos por orientação a objetos, não por GC. A origem da memória do array e garantir que você não escreva fora dele são conceitos ortogonais.
Edit: Vale a pena notar que técnicas mais avançadas podem evitar a necessidade de qualquer forma de alocação dinâmica de memória. Por exemplo, considere o uso disso , que implementa a combinação Y em C ++ sem nenhuma alocação dinâmica.
fonte
A "liberdade de se preocupar com a liberação de recursos" que as línguas coletadas pelo lixo supostamente fornecem é em grande parte uma ilusão. Continue adicionando itens em um mapa sem nunca remover nenhum, e você logo entenderá do que estou falando.
De fato, vazamentos de memória são bastante frequentes em programas escritos em linguagens GCed, porque essas linguagens tendem a tornar os programadores preguiçosos, e os fazem adquirir uma falsa sensação de segurança de que a linguagem sempre (de alguma forma mágica) cuidará de todos os objetos que eles não deseja mais pensar nisso.
A coleta de lixo é simplesmente um recurso necessário para linguagens que têm outro objetivo mais nobre: tratar tudo como um ponteiro para um objeto e, ao mesmo tempo, esconder do programador o fato de ser um ponteiro, para que o programador não possa confirmar suicídio, tentando aritmética ponteiro e afins. Tudo o que é um objeto significa que as linguagens GCed precisam alocar objetos com muito mais frequência do que as linguagens que não são GCed, o que significa que se elas colocassem o ônus de desalocar esses objetos no programador, elas seriam imensamente pouco atraentes.
Além disso, a coleta de lixo é útil para fornecer ao programador a capacidade de escrever código restrito, manipular objetos dentro de expressões, de maneira funcional de programação, sem precisar dividir as expressões em instruções separadas para permitir a desalocação de todos os único objeto que participa da expressão.
Além de tudo isso, observe que, no começo da minha resposta, escrevi "é em grande parte uma ilusão". Eu não escrevi que é uma ilusão. Eu nem escrevi que é principalmente uma ilusão. A coleta de lixo é útil para tirar do programador a tarefa servil de cuidar da desalocação de seus objetos. Portanto, nesse sentido, é um recurso de produtividade.
fonte
O coletor de lixo não resolve nenhum "erro". É uma parte necessária de algumas semânticas de linguagens de alto nível. Com um GC, é possível definir níveis mais altos de abstrações, como fechamentos lexicais e similares, enquanto que com um gerenciamento manual de memória essas abstrações ficarão com vazamentos, vinculadas desnecessariamente aos níveis mais baixos de gerenciamento de recursos.
Um "princípio de propriedade única", mencionado nos comentários, é um bom exemplo de uma abstração tão vazada. Um desenvolvedor não deve se preocupar com o número de links para qualquer instância específica da estrutura de dados elementares; caso contrário, qualquer parte do código não seria genérica e transparente sem um grande número de limitações e requisitos adicionais (não visíveis diretamente no próprio código) . Esse código não pode ser composto por um código de nível superior, que é uma violação intolerável do princípio da separação de camadas de responsabilidade (um elemento essencial da engenharia de software, que infelizmente não é respeitado pela maioria dos desenvolvedores de nível inferior).
fonte
Realmente, gerenciar sua própria memória é apenas mais uma fonte potencial de erros.
Se você esquecer uma chamada
free
(ou qualquer que seja o equivalente no idioma que você estiver usando), seu programa poderá passar em todos os testes, mas vazará memória. E em um programa moderadamente complexo, é muito fácil ignorar uma chamadafree
.fonte
free
não é a pior coisa. No iníciofree
é muito mais devastador.free
!malloc
efree
não eram o caminho a seguir eram muito lentos para serem úteis para qualquer coisa. Você precisa compará-lo a uma abordagem moderna que não seja de GC, como o RAII.O recurso manual não é apenas tedioso, mas também difícil de depurar. Em outras palavras, não apenas é entediante acertar, mas também quando você erra, não é óbvio onde está o problema. Isso ocorre porque, diferentemente da divisão por zero, por exemplo, os efeitos do erro aparecem longe da fonte do erro, e conectar os pontos exige tempo, atenção e experiência.
fonte
Acho que a coleta de lixo recebe muito crédito por melhorias de idioma que nada têm a ver com o GC, além de fazer parte de uma grande onda de progresso.
O único benefício sólido para o GC que eu conheço é que você pode liberar um objeto em seu programa e saber que ele desaparecerá quando todos terminarem. Você pode passá-lo para o método de outra classe e não se preocupar com isso. Você não se importa com que outros métodos ele é passado ou com que outras classes o referenciam. (Vazamentos de memória são de responsabilidade da classe que faz referência a um objeto, não da classe que o criou.)
Sem o GC, é necessário rastrear todo o ciclo de vida da memória alocada. Toda vez que você passa um endereço para cima ou para baixo na sub-rotina que o criou, você tem uma referência descontrolada a essa memória. Nos velhos tempos, mesmo com apenas um encadeamento, a recursão e um sistema operacional básico (Windows NT) tornavam impossível controlar o acesso à memória alocada. Eu tive que montar o método livre em meu próprio sistema de alocação para manter os blocos de memória por um tempo até que todas as referências fossem esclarecidas. O tempo de espera era pura adivinhação, mas funcionou.
Portanto, esse é o único benefício que eu conheço, mas não poderia viver sem ele. Eu não acho que nenhum tipo de POO voe sem ele.
fonte
Vazamentos físicos
Vindo da extremidade C, que torna o gerenciamento de memória o mais manual e pronunciado possível, para compararmos extremos (o C ++ automatiza principalmente o gerenciamento de memória sem GC), eu diria "não realmente" no sentido de comparar com o GC quando trata de vazamentos . Um iniciante e às vezes até um profissional podem esquecer de escrever
free
para um determinadomalloc
. Definitivamente acontece.No entanto, existem ferramentas como a
valgrind
detecção de vazamentos que identificarão imediatamente, na execução do código, quando / onde esses erros ocorrerão até a linha exata do código. Quando isso é integrado ao IC, torna-se quase impossível mesclar esses erros e fácil como torta para corrigi-los. Portanto, nunca é grande coisa em nenhuma equipe / processo com padrões razoáveis.Concedido, pode haver alguns casos exóticos de execução que voam sob o radar dos testes onde
free
não foram chamados, talvez ao encontrar um erro de entrada externa obscuro como um arquivo corrompido, caso em que talvez o sistema vaze 32 bytes ou algo assim. Eu acho que isso definitivamente pode acontecer mesmo com bons padrões de teste e ferramentas de detecção de vazamento, mas também não seria tão crítico vazar um pouco de memória em algo que quase nunca acontece. Veremos um problema muito maior, onde podemos vazar recursos massivos, mesmo nos caminhos de execução comuns abaixo, de uma maneira que o GC não pode impedir.Também é difícil sem algo parecido com uma pseudo-forma de GC (contagem de referência, por exemplo) quando o tempo de vida de um objeto precisa ser estendido para alguma forma de processamento adiado / assíncrono, talvez por outro encadeamento.
Ponteiros pendurados
O problema real com formas mais manuais de gerenciamento de memória não é vazamento para mim. Quantos aplicativos nativos escritos em C ou C ++ sabemos que são realmente vazados? O kernel do Linux está com vazamento? MySQL? CryEngine 3? Estações de trabalho e sintetizadores de áudio digital? O Java VM vaza (é implementado no código nativo)? Photoshop?
Acho que quando olhamos ao redor, os aplicativos mais vazios tendem a ser aqueles escritos usando esquemas de GC. Mas, antes que isso seja considerado um golpe na coleta de lixo, o código nativo tem um problema significativo que não está relacionado a vazamentos de memória.
A questão para mim sempre foi a segurança. Mesmo quando
free
memorizamos através de um ponteiro, se houver outros ponteiros para o recurso, eles se tornarão ponteiros danificados (invalidados).Quando tentamos acessar os pontos negativos desses ponteiros pendentes, acabamos tendo um comportamento indefinido, embora quase sempre uma violação de segfault / acesso leve a uma falha imediata e forte.
Todos os aplicativos nativos listados acima têm um ou dois casos obscuros que podem levar a uma falha principalmente devido a esse problema, e há definitivamente uma boa parte dos aplicativos de má qualidade, escritos em código nativo, que são muito pesados e frequentemente em grande parte devido a esse problema.
... e é porque o gerenciamento de recursos é difícil, independentemente de você usar o GC ou não. A diferença prática geralmente é vazar (GC) ou travar (sem GC) em face de um erro que leva à má administração de recursos.
Gerenciamento de recursos: coleta de lixo
O gerenciamento complexo de recursos é um processo manual difícil, não importa o quê. O GC não pode automatizar nada aqui.
Vamos dar um exemplo em que temos esse objeto, "Joe". Joe é referenciado por várias organizações das quais ele é membro. Todo mês, mais ou menos, eles extraem uma taxa de associação do cartão de crédito.
Também temos uma referência a Joe para controlar sua vida. Digamos que, como programadores, não precisamos mais do Joe. Ele está começando a nos incomodar e não precisamos mais dessas organizações que ele pertence para perder tempo lidando com ele. Então, tentamos limpá-lo da face da terra removendo sua referência da linha da vida.
... mas espere, estamos usando a coleta de lixo. Toda referência forte a Joe o manterá por perto. Portanto, também removemos referências a ele das organizações às quais ele pertence (cancelando a inscrição).
... exceto gritos, esquecemos de cancelar a assinatura de sua revista! Agora, Joe permanece na memória, incomodando-nos e consumindo recursos, e a empresa da revista também acaba continuando a processar a associação de Joe todos os meses.
Esse é o principal erro que pode fazer com que muitos programas complexos escritos usando esquemas de coleta de lixo vazem e comecem a usar mais e mais memória quanto mais tempo eles executam, e possivelmente mais e mais processamento (a assinatura periódica da revista). Eles esqueceram de remover uma ou mais dessas referências, impossibilitando que o coletor de lixo fizesse sua mágica até que todo o programa fosse desligado.
O programa não falha, no entanto. É perfeitamente seguro. Isso só vai manter a memória e Joe ainda vai demorar. Para muitas aplicações, esse tipo de comportamento com vazamento, no qual colocamos mais e mais memória / processamento em questão, pode ser muito preferível a uma falha grave, especialmente considerando a quantidade de memória e capacidade de processamento que nossas máquinas possuem atualmente.
Gerenciamento de Recursos: Manual
Agora vamos considerar a alternativa em que usamos ponteiros para Joe e o gerenciamento manual de memória, assim:
Esses links azuis não gerenciam a vida de Joe. Se queremos removê-lo da face da terra, solicitamos manualmente para destruí-lo, assim:
Agora, isso normalmente nos deixaria com ponteiros pendurados em todo o lugar, então vamos remover os ponteiros para Joe.
... gritos, cometemos o mesmo erro novamente e esquecemos de cancelar a assinatura da revista de Joe!
Exceto agora que temos um ponteiro pendente. Quando a assinatura da revista tenta processar a taxa mensal de Joe, o mundo inteiro vai explodir - normalmente temos o acidente instantâneo.
Esse mesmo erro básico de má administração de recursos, em que o desenvolvedor esqueceu de remover manualmente todos os ponteiros / referências a um recurso, pode levar a muitas falhas em aplicativos nativos. Eles não monopolizam a memória por mais tempo que executam normalmente, porque geralmente quebram completamente nesse caso.
Mundo real
Agora, o exemplo acima está usando um diagrama ridiculamente simples. Um aplicativo do mundo real pode exigir milhares de imagens unidas para cobrir um gráfico completo, com centenas de tipos diferentes de recursos armazenados em um gráfico de cena, recursos de GPU associados a alguns deles, aceleradores vinculados a outros, observadores distribuídos em centenas de plugins assistindo a vários tipos de entidades na cena em busca de mudanças, observadores observando observadores, áudios sincronizados com animações etc. Portanto, pode parecer fácil evitar o erro que descrevi acima, mas geralmente não é tão simples assim no mundo real base de código de produção para um aplicativo complexo que abrange milhões de linhas de código.
A chance de alguém, algum dia, administrar mal os recursos em algum lugar dessa base de código tende a ser bastante alta e essa probabilidade é a mesma com ou sem GC. A principal diferença é o que acontecerá como resultado desse erro, que também afeta potencialmente a rapidez com que esse erro será detectado e corrigido.
Crash vs. Leak
Agora qual é o pior? Um acidente imediato ou um vazamento silencioso de memória onde Joe simplesmente permanece misteriosamente?
A maioria pode responder ao último, mas digamos que este software foi projetado para funcionar por horas a fio, possivelmente dias, e cada um desses Joe e Jane que adicionamos aumenta o uso de memória do software em um gigabyte. Não é um software de missão crítica (falhas na verdade não matam usuários), mas um software de desempenho crítico.
Nesse caso, uma falha grave que aparece imediatamente durante a depuração, indicando o erro que você cometeu, pode ser preferível a apenas um software com vazamento que pode até voar sob o radar do seu procedimento de teste.
Por outro lado, se é um software de missão crítica em que o desempenho não é o objetivo, apenas não falha por nenhum meio possível, o vazamento pode ser realmente preferível.
Referências fracas
Existe uma espécie de híbrido dessas idéias disponíveis nos esquemas de GC conhecidos como referências fracas. Com referências fracas, podemos ter todas essas organizações com referência fraca a Joe, mas não impedir que ele seja removido quando a referência forte (proprietário / linha de vida de Joe) desaparecer. No entanto, temos o benefício de poder detectar quando Joe não está mais presente nessas referências fracas, o que nos permite obter um tipo de erro facilmente reproduzível.
Infelizmente, as referências fracas não são usadas tanto quanto provavelmente deveriam ser usadas; muitas vezes, aplicativos complexos de GC podem ser suscetíveis a vazamentos, mesmo que sejam potencialmente muito menos impactantes do que um aplicativo C complexo, por exemplo.
De qualquer forma, se o GC facilita ou dificulta sua vida depende de quão importante é para o seu software evitar vazamentos e se trata ou não de um gerenciamento complexo de recursos desse tipo.
No meu caso, trabalho em um campo crítico para o desempenho, onde os recursos abrangem centenas de megabytes a gigabytes, e não liberar essa memória quando os usuários solicitarem o descarregamento devido a um erro como o descrito acima pode ser menos preferível a uma falha. Os travamentos são fáceis de detectar e reproduzir, tornando-os frequentemente o tipo de bug favorito do programador, mesmo que seja o menos favorito do usuário, e muitas dessas falhas aparecem com um procedimento de teste sensato antes mesmo de chegarem ao usuário.
Enfim, essas são as diferenças entre o GC e o gerenciamento manual de memória. Para responder sua pergunta imediata, eu diria que o gerenciamento manual de memória é difícil, mas tem muito pouco a ver com vazamentos, e tanto o GC quanto as formas manuais de gerenciamento de memória ainda são muito difíceis quando o gerenciamento de recursos não é trivial. O GC sem dúvida tem um comportamento mais complicado aqui, onde o programa parece estar funcionando bem, mas consome cada vez mais recursos. O formulário manual é menos complicado, mas vai travar e queimar muito tempo com erros como o mostrado acima.
fonte
Aqui está uma lista de problemas enfrentados pelos programadores de C ++ ao lidar com memória:
Como você pode ver, a memória heap está resolvendo muitos problemas existentes, mas causa complexidade adicional. O GC foi projetado para lidar com parte dessa complexidade. (desculpe se alguns nomes de problemas não são os nomes corretos para esses problemas - às vezes é difícil descobrir o nome correto)
fonte