Ask Learn
Preview
Ask Learn is an AI assistant that can answer questions, clarify concepts, and define terms using trusted Microsoft documentation.
Please sign in to use Ask Learn.
Sign inThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Você já usou o comando GC.Collect? Há inúmeros casos que esse comando resolve problemas de memory leak.
Entretanto, esse procedimento é somente uma solução temporária e não resolve a real causa raiz. Pense: se o Garbage Collector (GC) do .NET faz a limpeza automática de memória, então por que rodá-lo manualmente?
Nesse artigo, vou abordar 3 pontos: Memory leak, Unmanaged code, Finalizers. Depois explico qual a relação com o GC.
No C/C++, a memória alocada deve ser liberada posteriormente:
A possibilidade de memory leak (vazamento de memória) existe quando o programador deixa de chamar a função de free/delete correspondente ao bloco de memória.
Diferente do C/C++, consideramos o .NET como código gerenciado (managed code), pois não existe a necessidade de liberar explicitamente a memória. Não existe free ou delete. Podemos fazer alocações de memória sem nos preocuparmos com a forma de desalocação, pois toda “memória gerenciada” é liberada automaticamente pelo Garbage Collector. Isso diminui os riscos de memory leak.
Ainda fica a pergunta: por que rodar o GC.Collect manualmente em .NET?
Você pode escrever um programa 100% em C#, mas sempre haverá trechos do runtime que dependem de recursos não-gerenciados. Por exemplo, veja os trechos abaixos:
StreamReader e SqlConnection são objetos gerenciado e, portanto, o GC faz a limpeza da memória associada. Entretanto, esses objetos usam recursos nativos do Sistema Operacional. No Windows, StreamReader possui uma referência a um handle de arquivo, porque internamente ele chama as funções do CreateFileEx do Kernel32.dll. De forma semelhante, o SqlConnection faz referência a uma DLL nativa (escrita em C++), que por sua vez abre sockets TCP via WinSock2.
Portanto, ambos os objetos dependem de recursos nativos e não-gerenciados, que não são da responsabilidade do Garbage Collector.
Os objetos do .NET possuem destrutores denominados de Finalizers. Esses métodos são chamados antes do Garbage Collector e são responsáveis por liberar a memória não-gerenciada relacionado ao unmanged code.
System.Object: Finalize method
https://msdn.microsoft.com/en-us/library/system.object.finalizeFinalizers (C# programming guide)
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/destructors
O conceito de destrutor no C++ é bem conhecido. Sempre quando um objeto é desalocado, o destrutor é chamado e depois a memória é devolvida.
Entretanto, o comportamento no C# é ligeiramente diferente. O método Finalize roda em uma thread dedicada chamada de Finalizer Thread e não ocorre de forma determinística. Existe inclusive uma lista de objetos a serem finalizados:
Somente objetos que possuem recursos não-gerenciados entram na fila.
É importante lembrar que o Garbage Collector (GC) e a Finalizer Thread trabalham em paralelo. Ambos são não-determinísticos, ou seja, não é possível determinar o momento em que rodam. Entretanto, existe uma ordem: a remoção do objeto pelo GC ocorre somente depois de sua finalização.
Em muitos casos, os "problemas de memory leaks do .NET" estão associados a uma longa fila de finalização. Enquanto a thread de finalização está suspensa, essa fila pode crescer e aumentar o consumo de recursos não-gerenciados.
A conclusão é que o GC.Collect resolve o problema de memory leak associado com unmanaged resource/code, que não passou ainda pelo finalizer.
Portanto, o memory leak ocorre somente nessas condições:
A solução é que normalmente adotar o Dispose pattern. Isso significa que os recursos não-gerenciados podem ser liberados de forma determinística através do método Dispose do objeto.
Assim como citado no exemplo anterior, os objetos StreamReader e SqlConnection possuem recursos nativos e que devem ser desalocados manualmente. Embora o finalizer da classe faça a liberação desses recursos, o ideal é sempre chamar o método Dispose depois de usar o objeto.
Nos próximos artigos, vou falar sobre o uso do try-finally para garantir que os recursos sejam sempre desalocados.
Ask Learn is an AI assistant that can answer questions, clarify concepts, and define terms using trusted Microsoft documentation.
Please sign in to use Ask Learn.
Sign in