Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este artigo aplica-se a: ✔️ SDK do .NET Core 3.1 e versões posteriores
A memória pode vazar quando seu aplicativo faz referência a objetos que não precisam mais para executar a tarefa desejada. A referência a esses objetos impede que o coletor de lixo recupere a memória usada. Isso pode resultar em degradação do desempenho e uma OutOfMemoryException exceção sendo lançada.
Este tutorial demonstra as ferramentas para analisar um vazamento de memória em um aplicativo .NET usando as ferramentas da CLI de diagnóstico do .NET. Se você estiver no Windows, poderá usar as ferramentas de diagnóstico de memória do Visual Studio para depurar o vazamento de memória.
Este tutorial utiliza uma aplicação de exemplo que vaza memória intencionalmente, para fins de exercício. Você também pode analisar aplicativos que vazam memória involuntariamente.
Neste tutorial, você irá:
- Examine o uso de memória gerida com dotnet-counters.
- Gere um arquivo de despejo.
- Analise o uso de memória usando o arquivo de despejo.
Pré-requisitos
O tutorial usa:
- SDK do .NET Core 3.1 ou uma versão posterior.
- dotnet-counters para verificar o uso de memória gerida.
- dotnet-dump para coletar e analisar um arquivo de despejo (inclui a extensão de depuração SOS).
- Uma aplicação de destino de depuração de exemplo para diagnosticar.
O tutorial pressupõe que os aplicativos e ferramentas de exemplo estão instalados e prontos para uso.
Se seu aplicativo estiver executando uma versão do .NET mais antiga que o .NET 9, a interface do usuário de saída dos contadores de pontos terá uma aparência ligeiramente diferente; Consulte contadores de pontos para obter detalhes.
Examinar o uso de memória gerenciada
Antes de começar a recolher dados de diagnóstico para ajudar a identificar a causa deste cenário, verifique se está realmente a observar um vazamento de memória (crescimento no uso de memória). Você pode usar a ferramenta dotnet-counters para confirmar isso.
Abra uma janela do console e navegue até o diretório onde você baixou e descompactou o destino de depuração de exemplo. Execute o destino:
dotnet run
Em um console separado, localize o ID do processo:
dotnet-counters ps
A saída deve ser semelhante a:
4807 DiagnosticScena /home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios
Observação
Se o comando anterior não funcionar ou não for encontrado, você provavelmente precisará instalar a dotnet-counters ferramenta primeiro. Use o seguinte comando:
dotnet tool install --global dotnet-counters
Agora, verifique o uso de memória gerenciada com a ferramenta dotnet-counters . O --refresh-interval especifica o número de segundos entre as atualizações:
dotnet-counters monitor --refresh-interval 1 -p 4807
A saída ao vivo deve ser semelhante a:
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[System.Runtime]
dotnet.assembly.count ({assembly}) 111
dotnet.gc.collections ({collection})
gc.heap.generation
------------------
gen0 1
gen1 0
gen2 0
dotnet.gc.heap.total_allocated (By) 4,431,712
dotnet.gc.last_collection.heap.fragmentation.size (By)
gc.heap.generation
------------------
gen0 803,576
gen1 15,456
gen2 0
loh 0
poh 0
dotnet.gc.last_collection.heap.size (By)
gc.heap.generation
------------------
gen0 811,960
gen1 1,214,720
gen2 0
loh 0
poh 24,528
dotnet.gc.last_collection.memory.committed_size (By) 4,296,704
dotnet.gc.pause.time (s) 0.003
dotnet.jit.compilation.time (s) 0.329
dotnet.jit.compiled_il.size (By) 120,212
dotnet.jit.compiled_methods ({method}) 1,202
dotnet.monitor.lock_contentions ({contention}) 2
dotnet.process.cpu.count ({cpu}) 22
dotnet.process.cpu.time (s)
cpu.mode
--------
system 0.344
user 0.344
dotnet.process.memory.working_set (By) 64,331,776
dotnet.thread_pool.queue.length ({work_item}) 0
dotnet.thread_pool.thread.count ({thread}) 0
dotnet.thread_pool.work_item.count ({work_item}) 7
dotnet.timer.count ({timer}) 0
Focando nesta linha:
dotnet.gc.last_collection.memory.committed_size (By) 4,296,704
Pode ver que a memória do heap gerenciado é de 4 MB imediatamente após a inicialização.
Agora, vá para o URL https://localhost:5001/api/diagscenario/memleak/20000.
Observe que o uso de memória cresceu para mais de 20 MB.
dotnet.gc.last_collection.memory.committed_size (By) 21,020,672
Ao observar o uso da memória, você pode dizer com segurança que a memória está crescendo ou vazando. O próximo passo é coletar os dados certos para análise de memória.
Gerar despejo de memória
Ao analisar possíveis vazamentos de memória, você precisa acessar a pilha de memória do aplicativo para analisar o conteúdo da memória. Olhando para as relações entre objetos, você cria teorias sobre por que a memória não está sendo liberada. Uma fonte comum de dados de diagnóstico é um dump de memória no Windows ou o core dump equivalente no Linux. Para gerar um despejo de um aplicativo .NET, você pode usar a ferramenta dotnet-dump .
Usando o destino de depuração de exemplo iniciado anteriormente, execute o seguinte comando para gerar um dump de núcleo do Linux:
dotnet-dump collect -p 4807
O resultado é um dump principal localizado na mesma pasta.
Writing minidump with heap to ./core_20190430_185145
Complete
Observação
Para uma comparação ao longo do tempo, deixe o processo original continuar em execução depois de coletar o primeiro despejo e coletar um segundo despejo da mesma maneira. Tu terias então dois dumps ao longo de um período de tempo que podes comparar para veres onde o uso de memória está a aumentar.
Reinicie o processo falhado
Uma vez que o despejo é coletado, você deve ter informações suficientes para diagnosticar o processo com falha. Se o processo com falha estiver sendo executado em um servidor de produção, agora é o momento ideal para a correção de curto prazo, reiniciando o processo.
Neste tutorial, já terminaste com o alvo de depuração de exemplo e podes fechá-lo. Navegue até o terminal que iniciou o servidor e pressione Ctrl+C.
Analise o dump principal
Agora que você tem um dump principal gerado, use a ferramenta dotnet-dump para analisar o dump:
dotnet-dump analyze core_20190430_185145
Onde core_20190430_185145 é o nome do dump principal que você deseja analisar.
Observação
Se você vir um erro reclamando que libdl.so não pode ser encontrado, talvez seja necessário instalar o pacote libc6-dev . Para obter mais informações, consulte Pré-requisitos para .NET no Linux.
Ser-lhe-á apresentado um aviso onde pode introduzir comandos SOS . Comumente, a primeira coisa que você quer observar é o estado geral da pilha gerenciada:
> dumpheap -stat
Statistics:
MT Count TotalSize Class Name
...
00007f6c1eeefba8 576 59904 System.Reflection.RuntimeMethodInfo
00007f6c1dc021c8 1749 95696 System.SByte[]
00000000008c9db0 3847 116080 Free
00007f6c1e784a18 175 128640 System.Char[]
00007f6c1dbf5510 217 133504 System.Object[]
00007f6c1dc014c0 467 416464 System.Byte[]
00007f6c21625038 6 4063376 testwebapi.Controllers.Customer[]
00007f6c20a67498 200000 4800000 testwebapi.Controllers.Customer
00007f6c1dc00f90 206770 19494060 System.String
Total 428516 objects
Aqui pode ver que a maioria dos objetos é ou objetos String ou objetos Customer.
Você pode usar o dumpheap comando novamente com a tabela de método (MT) para obter uma lista de todas as String instâncias:
> dumpheap -mt 00007f6c1dc00f90
Address MT Size
...
00007f6ad09421f8 00007faddaa50f90 94
...
00007f6ad0965b20 00007f6c1dc00f90 80
00007f6ad0965c10 00007f6c1dc00f90 80
00007f6ad0965d00 00007f6c1dc00f90 80
00007f6ad0965df0 00007f6c1dc00f90 80
00007f6ad0965ee0 00007f6c1dc00f90 80
Statistics:
MT Count TotalSize Class Name
00007f6c1dc00f90 206770 19494060 System.String
Total 206770 objects
Agora você pode usar o gcroot comando em uma System.String instância para ver como e por que o objeto está enraizado:
> gcroot 00007f6ad09421f8
Thread 3f68:
00007F6795BB58A0 00007F6C1D7D0745 System.Diagnostics.Tracing.CounterGroup.PollForValues() [/_/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/CounterGroup.cs @ 260]
rbx: (interior)
-> 00007F6BDFFFF038 System.Object[]
-> 00007F69D0033570 testwebapi.Controllers.Processor
-> 00007F69D0033588 testwebapi.Controllers.CustomerCache
-> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
-> 00007F6C000148A0 testwebapi.Controllers.Customer[]
-> 00007F6AD0942258 testwebapi.Controllers.Customer
-> 00007F6AD09421F8 System.String
HandleTable:
00007F6C98BB15F8 (pinned handle)
-> 00007F6BDFFFF038 System.Object[]
-> 00007F69D0033570 testwebapi.Controllers.Processor
-> 00007F69D0033588 testwebapi.Controllers.CustomerCache
-> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
-> 00007F6C000148A0 testwebapi.Controllers.Customer[]
-> 00007F6AD0942258 testwebapi.Controllers.Customer
-> 00007F6AD09421F8 System.String
Found 2 roots.
Você pode ver que o String é diretamente mantido pelo Customer objeto e indiretamente mantido por um CustomerCache objeto.
Você pode continuar despejando objetos para ver que a maioria dos String objetos segue um padrão semelhante. Neste ponto, a investigação forneceu informações suficientes para identificar a causa raiz em seu código.
Este procedimento geral permite-lhe identificar a origem das principais fugas de memória.
Limpeza de recursos
Neste tutorial, você iniciou um servidor Web de exemplo. Este servidor deve ter sido desligado conforme explicado na seção Reiniciar o processo com falha .
Você também pode excluir o arquivo de despejo que foi criado.
Ver também
- dotnet-trace para listar processos
- dotnet-counters para verificar o uso de memória gerenciada
- dotnet-dump para coletar e analisar um arquivo de despejo
- dotnet/diagnostics
- Usar o Visual Studio para depurar vazamentos de memória