Anatomia de um “vazamento de memória”

172

Na perspectiva do .NET:

  • O que é um vazamento de memória ?
  • Como você pode determinar se o seu aplicativo está vazando? Quais são os efeitos?
  • Como você pode evitar um vazamento de memória?
  • Se o seu aplicativo tiver vazamento de memória, ele desaparece quando o processo sai ou é interrompido? Ou o vazamento de memória em seu aplicativo afeta outros processos no sistema, mesmo após a conclusão do processo?
  • E o código não gerenciado acessado via COM Interop e / ou P / Invoke?
huseyint
fonte

Respostas:

110

A melhor explicação que eu já vi está no capítulo 7 do e-book gratuito Foundations of Programming .

Basicamente, no .NET, ocorre um vazamento de memória quando os objetos referenciados são enraizados e, portanto, não podem ser coletados com lixo. Isso ocorre acidentalmente quando você mantém referências além do escopo pretendido.

Você saberá que há vazamentos quando começar a obter OutOfMemoryExceptions ou o uso de memória vai além do que você esperaria (o PerfMon possui bons contadores de memória).

Entender o modelo de memória do .NET é sua melhor maneira de evitá-lo. Especificamente, entendendo como o coletor de lixo funciona e como as referências funcionam - novamente, refiro-lhe o capítulo 7 do e-book. Além disso, esteja atento às armadilhas comuns, provavelmente os eventos mais comuns. Se o objeto A é registrado para um evento no objeto B , então objeto Um vai ficar por até objeto B desaparece porque B contém uma referência para um . A solução é cancelar o registro de seus eventos quando terminar.

Obviamente, um bom perfil de memória permitirá que você veja seus gráficos de objetos e explore o aninhamento / referência de seus objetos para ver de onde vêm as referências e qual objeto raiz é responsável ( perfil de formigas de porta vermelha , perfil de formigas de porta vermelha , JetBrains dotMemory, memprofiler são realmente bons opções, ou você pode usar o WinDbg e o SOS somente em texto , mas eu recomendo fortemente um produto comercial / visual, a menos que você seja um verdadeiro guru).

Acredito que o código não gerenciado está sujeito a seus vazamentos de memória típicos, exceto que as referências compartilhadas são gerenciadas pelo coletor de lixo. Eu posso estar errado sobre este último ponto.

Karl Seguin
fonte
11
Oh você gosta de livro, não é? Vi o autor aparecer de vez em quando sobre o stackoverflow.
9139 Johnno Nolan
Alguns objetos .NET também podem se auto-enraizar e tornar-se incobráveis. Tudo o que for descartável deve ser descartado por causa disso.
Kyoryu
1
@kyoryu: Como um objeto se enraíza?
Andrei Rînea 22/10/10
2
@Andrei: Eu acho que um Thread em execução é talvez o melhor exemplo de um objeto fazendo root. Um objeto que coloca uma referência a si mesmo em um local não público estático (como se inscrever em um evento estático ou implementar um singleton pela inicialização de campo estático) também pode ter se enraizado, pois não há uma maneira óbvia de ... ... arrancá-lo da amarração.
precisa saber é o seguinte
@ Jeffry, esta é uma maneira pouco convencional de descrever o que está acontecendo e eu gosto!
Exitos
35

Estritamente falando, um vazamento de memória está consumindo memória que "não é mais usada" pelo programa.

"Não é mais usado" tem mais de um significado, pode significar "não mais referência a ele", ou seja, totalmente irrecuperável, ou pode ser referenciado, recuperável, sem uso, mas o programa mantém as referências de qualquer maneira. Somente o posterior se aplica ao .Net para objetos gerenciados perfeitamente . No entanto, nem todas as classes são perfeitas e, em algum momento, uma implementação não gerenciada subjacente pode vazar recursos permanentemente para esse processo.

Em todos os casos, o aplicativo consome mais memória do que o estritamente necessário. Os efeitos colaterais, dependendo da quantidade vazada, podem ir de zero a desaceleração causada por coleta excessiva, a uma série de exceções de memória e finalmente a um erro fatal seguido pelo encerramento forçado do processo.

Você sabe que um aplicativo tem um problema de memória quando o monitoramento mostra que mais e mais memória é alocada ao seu processo após cada ciclo de coleta de lixo . Nesse caso, você está mantendo muita memória ou alguma implementação não gerenciada subjacente está vazando.

Para a maioria dos vazamentos, os recursos são recuperados quando o processo é finalizado; no entanto, alguns recursos nem sempre são recuperados em alguns casos precisos, as alças do cursor GDI são notórias por isso. Obviamente, se você tiver um mecanismo de comunicação entre processos, a memória alocada no outro processo não será liberada até que esse processo a libere ou termine.

Coincoin
fonte
32

Acho que as perguntas "o que é um vazamento de memória" e "quais são os efeitos" já foram respondidas bem, mas eu queria adicionar mais algumas coisas às outras perguntas ...

Como entender se o seu aplicativo vaza

Uma maneira interessante é abrir o perfmon e adicionar traços para # bytes em todos os heaps e coleções # Gen 2 , em cada caso, observando apenas o seu processo. Se o exercício de um recurso específico fizer com que o total de bytes aumente e a memória permaneça alocada após a próxima coleção da geração 2, você poderá dizer que o recurso vaza memória.

Como prevenir

Outras boas opiniões foram dadas. Gostaria de acrescentar que talvez a causa mais negligenciada dos vazamentos de memória .NET seja adicionar manipuladores de eventos a objetos sem removê-los. Um manipulador de eventos anexado a um objeto é uma forma de referência a esse objeto, portanto, impedirá a coleta mesmo depois que todas as outras referências tiverem passado. Lembre-se sempre de desanexar manipuladores de eventos (usando a -=sintaxe em C #).

O vazamento desaparece quando o processo é encerrado e a interoperabilidade COM?

Quando o processo é encerrado, toda a memória mapeada em seu espaço de endereço é recuperada pelo sistema operacional, incluindo todos os objetos COM servidos pelas DLLs. Comparativamente, raramente, objetos COM podem ser servidos de processos separados. Nesse caso, quando o processo terminar, você ainda poderá ser responsável pela memória alocada em qualquer processo do servidor COM usado.

Martin
fonte
19

Eu definiria vazamentos de memória como um objeto que não libera toda a memória alocada após a conclusão. Eu descobri que isso pode acontecer no seu aplicativo se você estiver usando a API do Windows e o COM (ou seja, código não gerenciado que possui um bug ou não está sendo gerenciado corretamente), na estrutura e em componentes de terceiros. Também descobri que não resolver o problema depois de usar certos objetos, como canetas, pode causar o problema.

Eu, pessoalmente, sofri exceções de falta de memória que podem ser causadas, mas não são exclusivas, para vazamentos de memória em aplicativos dot net. (OOM também pode vir da fixação, consulte Fixação Artical ). Se você não estiver recebendo erros de OOM ou precisar confirmar se há um vazamento de memória, a única maneira é criar um perfil no seu aplicativo.

Eu também tentaria garantir o seguinte:

a) Tudo o que implementa o Idisposable é descartado usando um bloco final ou a declaração de uso, incluindo pincéis, canetas etc.

b) Qualquer coisa que possua um método close é fechada novamente usando finalmente ou a instrução using (embora eu tenha encontrado usando nem sempre fecha, dependendo se você declarou o objeto fora da instrução using)

c) Se você estiver usando código não gerenciado / API do Windows, estas serão tratadas corretamente depois. (alguns têm métodos de limpeza para liberar recursos)

Espero que isto ajude.

John
fonte
19

Se você precisar diagnosticar um vazamento de memória no .NET, verifique estes links:

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

Esses artigos descrevem como criar um despejo de memória do seu processo e como analisá-lo para que você possa primeiro determinar se o vazamento não é gerenciado ou gerenciado e, se é gerenciado, como descobrir de onde vem.

A Microsoft também possui uma ferramenta mais nova para ajudar na geração de despejos de memória, para substituir o ADPlus, chamado DebugDiag.

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en

Eric Z Beard
fonte
15

A melhor explicação de como o coletor de lixo funciona está no Jeff Richters CLR via livro C # (cap. 20). A leitura disso fornece uma excelente base para entender como os objetos persistem.

Uma das causas mais comuns de enraizamento de objetos acidentalmente é conectando eventos fora de uma classe. Se você conectar um evento externo

por exemplo

SomeExternalClass.Changed += new EventHandler(HandleIt);

e esqueça de soltar-o quando descartá-lo; o SomeExternalClass tem uma referência à sua classe.

Como mencionado acima, o perfilador de memória SciTech é excelente para mostrar as raízes dos objetos que você suspeita estar vazando.

Mas também existe uma maneira muito rápida de verificar se um tipo específico é apenas usar o WnDBG (você pode até usá-lo na janela imediata do VS.NET enquanto estiver conectado):

.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

Agora faça algo que você acha que irá descartar os objetos desse tipo (por exemplo, feche uma janela). Aqui é útil ter um botão de depuração em algum lugar que será executado System.GC.Collect()algumas vezes.

Então corra !dumpheap -stat -type <TypeName>novamente. Se o número não caiu, ou não caiu tanto quanto o esperado, você tem uma base para uma investigação mais aprofundada. (Recebi essa dica de um seminário ministrado por Ingo Rammer ).

Gus Paul
fonte
14

Eu acho que em um ambiente gerenciado, um vazamento seria você manter uma referência desnecessária a um grande pedaço de memória.

Bernard
fonte
11

Por que as pessoas pensam que um vazamento de memória no .NET não é o mesmo que qualquer outro vazamento?

Um vazamento de memória ocorre quando você se conecta a um recurso e não o deixa ir. Você pode fazer isso na codificação gerenciada e na não gerenciada.

Em relação ao .NET e outras ferramentas de programação, houve idéias sobre a coleta de lixo e outras maneiras de minimizar situações que farão o seu aplicativo vazar. Mas o melhor método para evitar vazamentos de memória é que você precisa entender o modelo de memória subjacente e como as coisas funcionam na plataforma que você está usando.

Acreditar que o GC e outras magias limparão sua bagunça é o caminho mais curto para vazamentos de memória e será difícil encontrá-lo mais tarde.

Ao codificar de forma não gerenciada, você normalmente limpa, você sabe que os recursos de que se apossar serão de sua responsabilidade, e não do zelador.

Por outro lado, no .NET, muitas pessoas pensam que o GC limpará tudo. Bem, isso faz algum para você, mas você precisa ter certeza de que é assim. O .NET agrupa muitas coisas, portanto, você nem sempre sabe se está lidando com um recurso gerenciado ou não gerenciado e precisa ter certeza do que está lidando. O manuseio de fontes, recursos GDI, diretório ativo, bancos de dados, etc, geralmente é algo que você precisa observar.

Em termos gerenciados, colocarei meu pescoço em risco para dizer que desaparece assim que o processo é encerrado / removido.

Eu vejo muitas pessoas tendo isso, porém, e realmente espero que isso acabe. Você não pode pedir ao usuário que encerre seu aplicativo para limpar sua bagunça! Dê uma olhada em um navegador, que pode ser o IE, FF etc., abra, digamos, o Google Reader, deixe-o ficar por alguns dias e veja o que acontece.

Se você abrir outra guia no navegador, navegar para algum site e fechar a guia que hospedava a outra página que fez o navegador vazar, você acha que o navegador liberará a memória? Não é assim com o IE. No meu computador, o IE consumirá facilmente 1 GiB de memória em um curto período de tempo (cerca de 3-4 dias) se eu usar o Google Reader. Algumas notícias são ainda piores.

neslekkiM
fonte
10

Eu acho que em um ambiente gerenciado, um vazamento seria você manter uma referência desnecessária a um grande pedaço de memória.

Absolutamente. Além disso, não usar o método .Dispose () em objetos descartáveis, quando apropriado, pode causar vazamentos de mem. A maneira mais fácil de fazer isso é usando um bloco using, porque ele executa automaticamente .Dispose () no final:

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

E se você criar uma classe que está usando objetos não gerenciados, se não estiver implementando o IDisposable corretamente, poderá causar vazamento de memória para os usuários da sua classe.

Seibar
fonte
9

Todos os vazamentos de memória são resolvidos pelo encerramento do programa.

Vazamento de memória suficiente e o sistema operacional pode decidir resolver o problema em seu nome.

Josh
fonte
8

Concordo com Bernard quanto à .net o que seria um vazamento de mem.

Você pode criar um perfil do seu aplicativo para ver seu uso de memória e determinar que, se estiver gerenciando muita memória quando não deveria, você poderia dizer que há um vazamento.

Em termos gerenciados, colocarei meu pescoço em risco para dizer que desaparece assim que o processo é morto / removido.

O código não gerenciado é seu próprio animal e, se houver um vazamento dentro dele, ele seguirá um mem padrão. definição de vazamento.

Pat
fonte
7

Lembre-se também de que o .NET possui dois heaps, sendo um o grande heap de objetos. Acredito que objetos de aproximadamente 85k ou mais sejam colocados nessa pilha. Esse heap possui regras de vida útil diferentes das do heap normal.

Se você estiver criando grandes estruturas de memória (Dicionário ou Lista), seria prudente pesquisar quais são as regras exatas.

No que diz respeito à recuperação da memória na finalização do processo, a menos que você esteja executando o Win98 ou equivalente, tudo será liberado de volta ao sistema operacional na finalização. As únicas exceções são as coisas que são abertas entre processos e outro processo ainda tem o recurso aberto.

Objetos COM podem ser complicados. Se você sempre usar o IDisposepadrão, estará seguro. Mas já deparei com alguns assemblies de interoperabilidade implementados IDispose. A chave aqui é ligar Marshal.ReleaseCOMObjectquando você terminar. Os objetos COM ainda usam a contagem de referência COM padrão.

Joel Lucsy
fonte
6

Achei o .Net Memory Profiler uma ajuda muito boa para encontrar vazamentos de memória no .Net. Não é gratuito como o Microsoft CLR Profiler, mas é mais rápido e mais direto ao ponto na minha opinião. UMA

Lars Truijens
fonte