O perigo da concatenação de strings
Trabalho em uma grande instituição financeira e esta semana tivemos um problema sério de alto consumo de memória de um único processo, em um servidor de aplicação extremamente acessado. O consumo exagerado era de um processo de Cache, ele estava com quase 1GB de memória alocada.Bom, sendo um processo de Cache isto pode ser uma verdade em muitas situações, entretanto, a informação armazenada em Cache no momento, era de apenas 20 MB, portanto, 1GB alocado é algo fora dos padrões.
Descobrir memory leak não é uma tarefa muito fácil, realizar um Code Review pode ser uma solução, pois podemos percorrer o código e procurar por causas que podem determinar esta condição. Mas há um porem, se a aplicação for muito grande, isto pode demorar muito,e demorar a encontrar um problema de produção é muito grave para nosso setor de atuação.Quando há qualquer tipo de problema em produção, preferimos realizar análise de Dump por ser mais rápido.
O Dump do processo é gerado pelo pessoal de produção que nos encaminha para análise. Análise de Dump é uma técnica de debug onde analisamos a memória de um processo. No fim deste post, deixo algumas referencias para quem gosta de desafios.
Vamos lá, para realizar a análise vamos utilizar o programa WinDbg:
Pela opção File/Open Crache Dump, vamos carregar o Dump.
Agora vou carregar a DLL SOS.dll, esta DLL fornece comandos para manipular Dump de código gerenciado. Para cada versão do .Net Framework, há uma versão diferente. No caso, estou carregando a Dll do framework 2.0
Como é um memory leak, vou direto ao comando !dumpheap -stat. O objetivo deste comando é trazer estatísticas de consumo de memória por tipo. O resultado é expresso em bytes. Dependendo do tamanho do dump, o resultado poderá demorar um pouco. Por motivos de confidencialidade, estou ocultando algumas informações.
Achamos os vilões. Percebam que o tipo System.String ocupa 560 MB e temos em torno de 400 MB com objetos prontos para serem liberados. Somando, temos quase os 1GB de memória consumida. É um tanto estranho o tipo System.String ocupar tanto espaço não?Agora vamos recuperar todos os 281 mil objetos do tipo string. Para esta tarefa vamos usar o comando !dumpheap -mt 793308ec.
Como são milhares de objetos, vamos pesquisar por amostragem. A idéia é buscar a raiz de cada objeto. Para realizar esta busca, vamos utilizar o comando !gcroot <endereço do objeto>, no caso !gcroot 3dc31000.
Temos um suspeito agora, o CacheCleanup. Varrendo outros objetos percebi que todos têm a mesma raiz, ou seja, provavelmente o problema está lá. Abaixo um trecho do código danoso:
#region Access Log
Handler.LogHandler.Tracking("Access Method: " + this.GetType().ToString() + "->" + ((object)MethodBase.GetCurrentMethod()).ToString() + " ;");
#endregion Access Log
Como podemos ver, o código está cheio de concatenações em um trecho crítico do projeto que trata traces, e este é chamado milhares de vezes. Concatenação de string com o operador + não é uma boa prática devido à maneira que ocorre a manipulação de memória. Mais informações você pode encontrar neste site. Para corrigir, apenas substituí por String.Builder e o problema foi resolvido.
Links
Blog da Tess Fernandes tem tudo sobre debug
Debugging Tools for Windows
Conclusão
O que pode ser algo inofensivo, em cenários onde há milhões de chamadas em curto espaço de tempo pode se tornar uma dor de cabeça sem precedentes e custar muito $$$$$.
[]’s
Fabio Margarito