Anatomia de um “memory leaks”

Na perspectiva do .NET:

  • O que é um memory leaks ?
  • Como você pode determinar se o seu aplicativo vaza? Quais são os efeitos?
  • Como você pode evitar um memory leaks?
  • Se o seu aplicativo tem memory leaks, ele desaparece quando o processo é encerrado ou é eliminado? Ou os vazamentos de memory em seu aplicativo afetam outros processos no sistema mesmo após a conclusão do processo?
  • E quanto ao código não gerenciado acessado via COM Interop e / ou P / Invoke?

A melhor explicação que vi foi no Capítulo 7 do livro eletrônico gratuito Fundamentos da programação .

Basicamente, no .NET, um memory leaks ocorre quando objects referenciados são enraizados e, portanto, não podem ser coletados como 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 se o uso de sua memory exceder o esperado (o PerfMon tem bons contadores de memory).

Entender o modelo de memory do .NET é a melhor maneira de evitá-lo. Especificamente, entendendo como o coletor de lixo funciona e como as referências funcionam – novamente, refiro-me ao capítulo 7 do e-book. Além disso, esteja atento às armadilhas comuns, provavelmente os events mais comuns. Se o object A estiver registrado em um evento no object B , então o object A permanecerá até que o object B desapareça porque B contém uma referência a A. A solução é cancelar o registro de seus events quando terminar.

Naturalmente, um bom perfil de memory permitirá que você veja seus charts de object e explore o aninhamento / referência de seus objects para ver de onde vêm as referências e qual object raiz é responsável ( perfil de formigas vermelhas , JetBrains dotMemory, memprofiler são realmente bons escolhas, ou você pode usar o WinDbg e SOS somente 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 memory típicos, exceto que as referências compartilhadas são gerenciadas pelo coletor de lixo. Eu posso estar errado sobre este último ponto.

Estritamente falando, um memory leaks está consumindo memory 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”, isto é, totalmente irrecuperável, ou pode significar, referenciado, recuperável, não utilizado, mas o programa mantém as referências de qualquer maneira. Somente o posterior se aplica ao .Net para objects perfeitamente gerenciados . No entanto, nem todas as classs são perfeitas e, em algum momento, uma implementação não gerenciada subjacente pode vazar resources permanentemente para esse processo.

Em todos os casos, o aplicativo consome mais memory do que estritamente necessário. Os efeitos laterais, dependendo do volume vazado, poderiam passar de nenhum, para a lentidão causada pela coleta excessiva, para uma série de exceções de memory e, finalmente, um erro fatal seguido de encerramento forçado do processo.

Você sabe que um aplicativo tem um problema de memory quando o monitoramento mostra que mais e mais memory é alocada para seu processo após cada ciclo de garbage collection . Nesse caso, você está mantendo muito na memory ou alguma implementação subjacente não gerenciada está vazando.

Para a maioria dos vazamentos, os resources são recuperados quando o processo é finalizado, no entanto, alguns resources nem sempre são recuperados em alguns casos precisos, os identificadores de cursor GDI são notórios para isso. Obviamente, se você tiver um mecanismo de comunicação entre processos, a memory alocada no outro processo não será liberada até que o processo seja liberado ou encerrado.

Eu acho que as perguntas “o que é um memory leaks” e “quais são os efeitos” já foram respondidas bem, mas eu queria adicionar mais algumas coisas nas outras perguntas …

Como entender se o seu aplicativo vaza

Uma maneira interessante é abrir o perfmon e adicionar traços para # bytes em todas as collections heaps e # Gen 2 , em cada caso olhando apenas para o seu processo. Se o exercício de um determinado recurso fizer com que o total de bytes aumente e que a memory permaneça alocada após a próxima coleta de Gen 2, você poderá dizer que o recurso vazou memory.

Como prevenir

Outras boas opiniões foram dadas. Gostaria apenas de acrescentar que talvez a causa mais negligenciada de vazamentos de memory .NET seja adicionar manipuladores de events a objects sem removê-los. Um manipulador de events anexado a um object é uma forma de referência a esse object, portanto, impedirá a coleta mesmo depois de todas as outras referências terem sido eliminadas. Lembre-se sempre de desappend os manipuladores de events (usando a syntax -= em C #).

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

Quando o processo é encerrado, toda a memory mapeada em seu espaço de endereço é recuperada pelo sistema operacional, incluindo todos os objects COM fornecidos pelas DLLs. Comparativamente raramente, os objects COM podem ser atendidos a partir de processos separados. Nesse caso, quando o processo é encerrado, você ainda pode ser responsável pela memory alocada em qualquer processo do servidor COM usado.

Eu definiria vazamentos de memory como um object que não liberava toda a memory alocada após sua conclusão. Eu descobri que isso pode acontecer em seu aplicativo se você estiver usando o Windows API e 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. Eu também achei não ordenar depois de usar certos objects como canetas pode causar o problema.

Pessoalmente, sofri Exceções de Memória Excessiva que podem ser causadas, mas não são exclusivas de vazamentos de memory em aplicativos dot net. (OOM também pode vir da fixação veja Pinning Artical ). Se você não está recebendo erros OOM ou precisa confirmar se é um memory leaks, então a única maneira é fazer o perfil do seu aplicativo.

Eu também tentaria garantir o seguinte:

a) Tudo o que implementa Idisposable é descartado usando um bloco final ou a declaração de uso, que inclui pincéis, canetas, etc. (algumas pessoas argumentam que nada é necessário)

b) Qualquer coisa que tenha um método close é fechado novamente usando finalmente ou usando a instrução (embora eu ache que usar nem sempre fecha dependendo se você declarou o object fora da instrução using)

c) Se você estiver usando código não gerenciado / windows APIs, estes são tratados corretamente depois. (alguns têm methods de limpeza para liberar resources)

Espero que isto ajude.

Se você precisar diagnosticar um memory leaks no .NET, verifique estes links:

http://msdn.microsoft.com/pt-br/magazine/cc163833.aspx

http://msdn.microsoft.com/pt-br/magazine/cc164138.aspx

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

A Microsoft também tem uma ferramenta mais recente para auxiliar na geração de despejos de memory, para replace o ADPlus, chamado DebugDiag.

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

Usar o CLR Profiler da Microsoft http://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en é uma ótima maneira de determinar quais objects estão armazenando memory, o que o stream de execução leva para a criação desses objects, e também monitorar quais objects vivem onde estão no heap (fragmentação, LOH, etc.).

A melhor explicação de como funciona o coletor de lixo é em Jeff Richters CLR via C # book, (cap. 20). A leitura disso dá uma ótima base para entender como os objects persistem.

Uma das causas mais comuns de enraizar objects acidentalmente é conectando events em uma class. Se você ligar um evento externo

por exemplo

 SomeExternalClass.Changed += new EventHandler(HandleIt); 

e esqueça de soltá-lo quando você se desfizer, então SomeExternalClass tem uma ref a sua class.

Como mencionado acima, o profiler de memory SciTech é excelente para mostrar as raízes dos objects suspeitos de vazamento.

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

 .loadby sos mscorwks !dumpheap -stat -type  

Agora faça algo que você acha que irá dispor os objects desse tipo (por exemplo, fechar uma janela). É útil ter um botão de debugging em algum lugar que execute o System.GC.Collect() algumas vezes.

Em seguida, execute !dumpheap -stat -type novamente. Se o número não caiu ou não diminuiu tanto quanto você espera, então você tem uma base para investigações posteriores. (Recebi esta dica de um seminário de Ingo Rammer ).

Eu acho que em um ambiente gerenciado, um vazamento seria você manter uma referência desnecessária para um grande pedaço de memory ao redor.

Por que as pessoas acham que um memory leaks no .NET não é o mesmo que qualquer outro vazamento?

Um memory leaks é quando você anexa a um recurso e não o libera. Você pode fazer isso tanto na codificação gerenciada quanto na não gerenciada.

Em relação ao .NET e outras ferramentas de programação, existem ideias sobre garbage collection e outras maneiras de minimizar situações que farão com que seu aplicativo vaze. Mas o melhor método de evitar vazamentos de memory é que você precisa entender seu modelo de memory subjacente e como as coisas funcionam na plataforma que você está usando.

Acreditar que a GC e outras magias irão limpar sua bagunça é o caminho mais curto para vazamentos de memory, e será difícil encontrá-la mais tarde.

Ao codificar não gerenciado, você normalmente se certifica de limpar, você sabe que os resources que você toma, serão de sua responsabilidade limpar, não os do zelador.

No .NET, por outro lado, muitas pessoas pensam que o GC irá limpar tudo. Bem, faz algumas para você, mas você precisa ter certeza de que é assim. O .NET envolve muitas coisas, então você nem sempre sabe se está lidando com um recurso gerenciado ou não gerenciado, e precisa se certificar com o que está lidando. Lidar com fonts, resources GDI, diretórios ativos, bancos de dados, etc, geralmente é algo que você precisa observar.

Em termos gerenciados, vou colocar meu pescoço na linha para dizer que desaparece quando o processo é morto / removido.

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

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

Eu acho que em um ambiente gerenciado, um vazamento seria você manter uma referência desnecessária para um grande pedaço de memory ao redor.

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

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

E se você criar uma class que esteja usando objects não gerenciados, se não estiver implementando IDisposable corretamente, poderá estar causando vazamentos de memory para os usuários da sua class.

Eu vou concordar com Bernard quanto a .net o que seria um vazamento de mem.

Você poderia criar um perfil de seu aplicativo para ver seu uso de memory e determinar que, se ele gerencia muita memory quando não deveria, você poderia dizer que tem um vazamento.

Em termos gerenciados, vou colocar meu pescoço na linha para dizer que desaparece quando 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.

Todos os vazamentos de memory são resolvidos pela finalização do programa.

Vazamento de memory suficiente e o sistema operacional pode decidir resolver o problema em seu nome.

Também tenha em mente que o .NET tem dois heaps, sendo um deles o heap de objects grandes. Acredito que objects de cerca de 85k ou mais sejam colocados nessa pilha. Esse heap tem regras de tempo de vida diferentes do heap normal.

Se você estiver criando grandes estruturas de memory (do dictionary ou lista), seria prudente pesquisar quais são as regras exatas.

Quanto à recuperação da memory na finalização do processo, a menos que o Win98 ou o equivalente em execução funcionem, tudo é liberado de volta para o sistema operacional na terminação. As únicas exceções são coisas que são abertas em processo cruzado e outro processo ainda tem o recurso aberto.

Objetos COM podem ser complicados. Se você sempre usar o padrão IDispose , estará seguro. Mas eu corri através de algumas assembléias de interoperabilidade que implementam o IDispose . A chave aqui é chamar Marshal.ReleaseCOMObject quando estiver pronto. Os objects COM ainda usam contagem de referência COM padrão.

Eu encontrei .Net Memory Profiler uma ajuda muito boa quando encontrar vazamentos de memory no .net. Não é livre como o Microsoft CLR Profiler, mas é mais rápido e mais direto ao ponto na minha opinião. UMA

Uma definição é: Não é possível liberar memory inacessível, que não pode mais ser alocada para novo processo durante a execução do processo de alocação. Ele pode ser curado principalmente usando técnicas de GC ou detectado por ferramentas automatizadas.

Para mais informações, visite http://all-about-java-and-weblogic-server.blogspot.in/2014/01/what-is-memory-leak-in-java.html .