Assim como o cérebro desempenha um papel vital na vida humana, a memória é igualmente essencial para o funcionamento dos computadores. O seu sistema pode ficar incapaz de realizar tarefas caso a RAM disponível seja insuficiente.
A falta de RAM, juntamente com outros problemas relacionados à memória, pode ser desencadeada por fugas de memória. Por isso, vamos detalhar como identificar e solucionar esses vazamentos.
No entanto, antes de avançarmos, vamos aprofundar o nosso entendimento sobre o que são fugas de memória e a importância de corrigi-las.
O que são Fugas de Memória?
Imagine um parque de estacionamento junto a um centro comercial. Os carros são estacionados sem qualquer controlo sobre se os seus proprietários já terminaram as suas compras. Com o tempo, o estacionamento esgotar-se-á, não sobrando espaço para novos veículos, gerando congestionamento e comprometendo a eficiência geral do centro comercial.
Imagem de referência: prateeknima.medium.com
Em essência, o mesmo ocorre com os computadores!
As aplicações de computador, tal como os carros no estacionamento, podem não libertar a memória que utilizam depois de já não ser necessária. Isso sobrecarrega a memória, impedindo a execução de novas tarefas de forma fluida e resultando num erro comum conhecido como fuga de memória.
Um exemplo de código que ilustra uma fuga de memória:
void alocacao_memoria() { int *ptr = (int*)malloc(sizeof(int)); }
O fragmento de código C acima aloca uma porção de memória para armazenar um valor inteiro e associa essa localização de memória ao ponteiro ‘ptr’. Contudo, não existe uma instrução para desalocar essa memória, originando uma fuga de memória.
def recursao_infinita(): return recursao_infinita()
No código Python acima, não existe uma condição base para interromper a função. Como resultado, o código acima causa um estouro de pilha e fugas de memória.
Causas Comuns para Fugas de Memória
Negligência do Programador
A principal razão para uma fuga de memória é a negligência do programador.
Muitas vezes, os programadores alocam memória para dados, mas em alguns casos não libertam essa memória quando já não é necessária. Eventualmente, isso leva a que toda a memória fique ocupada, impossibilitando a execução de novas tarefas, resultando no que conhecemos como erro de “fuga de memória”.
Linguagens de Programação
Utilizar linguagens de programação que não dispõem de um sistema de gestão de memória integrado pode originar fugas de memória.
Linguagens de programação como Java incluem coletores de lixo integrados que automatizam a gestão da memória.
Por outro lado, o C++ não possui um coletor de lixo nativo. Portanto, o programador é responsável por gerir a memória manualmente, o que pode gerar fugas de memória caso se esqueça de limpar a memória.
Uso Excessivo de Cache
Dados, tarefas ou aplicações frequentemente utilizadas são armazenadas em cache para permitir um acesso mais rápido.
Isto pode originar um erro de fuga de memória se os itens ficarem armazenados em cache sem serem removidos, mesmo quando já estão desatualizados ou deixaram de corresponder aos padrões de utilização.
Utilização de Variáveis Globais
As variáveis globais mantêm os dados alocados durante toda a vida útil da aplicação. Como resultado, um uso intensivo de variáveis globais consome muita memória durante um período prolongado, causando fugas de memória.
Estruturas de Dados Ineficientes
Por vezes, os desenvolvedores criam as suas próprias estruturas de dados para implementar funcionalidades específicas. No entanto, ocorrem erros de fuga de memória se estas estruturas de dados não libertarem a memória utilizada.
Conexões Não Encerradas
Não fechar arquivos, bases de dados ou conexões de rede após o seu uso também pode originar erros de fuga de memória.
Consequências das Fugas de Memória
Desempenho Reduzido – Irá observar uma diminuição gradual no desempenho da aplicação ou sistema à medida que as fugas de memória se acumulam. Isto acontece porque a memória disponível para concluir as tarefas diminui, tornando a aplicação mais lenta.
Falhas de Aplicações – As aplicações ficam sem memória à medida que as fugas de memória aumentam. Eventualmente, quando não existe mais memória disponível, o programa encerra-se, resultando na perda de dados e falhas na aplicação.
Vulnerabilidades de Segurança – A falta de limpeza adequada de informações sensíveis, como senhas ou dados pessoais, após a sua utilização expõe esses dados a intrusos durante uma fuga de memória.
Esgotamento de Recursos – As aplicações ocupam mais espaço na RAM quando ficam sem memória devido às fugas de memória. Este aumento no consumo de recursos leva a uma redução no desempenho geral do sistema.
Como Detetar Fugas de Memória?
Inspeção Manual do Código
Analise o seu código-fonte procurando por situações em que a memória é alocada, mas não limpa após o uso. Procure por variáveis e objetos no código que utilizem memória, mas não a libertem quando deixam de ser necessários.
Adicionalmente, verifique as principais fontes de armazenamento de dados e certifique-se de que as estruturas de dados gerem a memória alocada adequadamente.
Análise Estática de Código
Existem diversas ferramentas de análise estática que analisam o código-fonte e detetam situações de fuga de memória.
Em geral, estas ferramentas rastreiam padrões, regras e erros comuns no código, permitindo identificar fugas de memória antes mesmo de elas ocorrerem.
Ferramentas de Análise Dinâmica
Estas ferramentas recorrem a uma abordagem dinâmica para analisar o código durante a sua execução e detetar fugas de memória.
As ferramentas de análise dinâmica monitorizam o comportamento em tempo de execução dos objetos e funções, bem como a sua utilização da memória. Este nível de detalhe torna estas ferramentas muito precisas na deteção de fugas de memória.
Ferramentas de Criação de Perfis
As ferramentas de criação de perfis fornecem informações detalhadas sobre a forma como a aplicação está a utilizar a memória.
Enquanto desenvolvedor, pode utilizar esta informação para analisar o uso de memória do programa e otimizar as técnicas de gestão de memória para evitar falhas de aplicações e problemas de degradação de memória.
Bibliotecas de Detecção de Fugas de Memória
Algumas linguagens de programação fornecem bibliotecas integradas ou de terceiros que auxiliam na deteção de fugas de memória no seu programa.
Por exemplo, o Java inclui um coletor de lixo para gestão da memória e o C++ oferece o CrtDbg para gestão de memória.
Além disso, existem bibliotecas especializadas como o LeakCanary, Valgrind ou YourKit, que lidam com fugas de memória em vários tipos de aplicações.
Como Corrigir Fugas de Memória?
Identificar Fugas de Memória
Para corrigir fugas de memória, é essencial identificá-las primeiramente.
Pode realizar uma inspeção manual ou utilizar ferramentas automatizadas para identificar se a sua aplicação está a perder memória. Pode testar os outros métodos de deteção de fugas de memória acima mencionados para identificar os problemas.
Identificar os Objetos que Causam a Fuga
Depois de confirmar que a aplicação está a perder memória, deverá procurar pelos objetos e estruturas de dados que estão a causar a fuga. É importante compreender como estes objetos recebem memória e onde deveriam libertá-la.
Criar Casos de Teste
Agora que já conseguiu restringir a localização da fuga de memória, crie um caso de teste para confirmar se identificou corretamente a origem da fuga e se o problema desaparece depois de corrigir os objetos específicos.
Corrigir o Código
Adicione o código de desalocação de memória para libertar a memória bloqueada pelos objetos com problemas identificados. Se o código de desalocação já existir, atualize-o para garantir que desaloca a memória utilizada adequadamente.
Testar Novamente
Recorra novamente a ferramentas de deteção de fugas ou testes automatizados para verificar se a aplicação funciona conforme o esperado e se não existe bloqueio de memória.
Adicionalmente, teste o desempenho e as funcionalidades da aplicação para confirmar que a atualização do código não afetou outros aspetos da aplicação.
Melhores Práticas para Prevenir Fugas de Memória
Seja um Programador Responsável
Ao escrever o código, deve ter sempre presente a importância de desalocar a memória ou libertar os ponteiros de memória. Isto irá minimizar os problemas de fugas de memória.
Recorda-se do código abaixo? Conforme mencionado no início do artigo, este código não inclui a desalocação de memória, o que leva a uma fuga de memória.
void alocacao_memoria() { int *ptr = (int*)malloc(sizeof(int)); }
Veja como, enquanto programador, pode desalocar a memória:
delete ptr;
Utilize Linguagens de Programação Equipadas
Linguagens de programação como Java ou Python incluem bibliotecas integradas de gestão de memória, como coletores de lixo, que lidam automaticamente com fugas de memória.
Embora possa haver situações que não sejam detetadas, estas ferramentas integradas cuidam da maioria dos casos, prevenindo potenciais fugas de memória.
Portanto, é recomendável utilizar linguagens de programação que incluam gestão de memória integrada.
Referências Circulares
Evite referências circulares no seu programa.
As referências circulares formam um ciclo fechado de objetos que se referem uns aos outros. Por exemplo, o objeto A refere-se a B, B refere-se a C e C refere-se novamente a A, criando um ciclo sem fim. Como tal, as referências circulares podem originar ciclos infinitos e fugas de memória.
Minimize o Uso de Variáveis Globais
Deverá evitar o uso de variáveis globais se estiver preocupado com a eficiência da memória. As variáveis globais ocupam memória durante toda a execução da aplicação, o que não é uma boa prática em termos de gestão de memória.
Assim, opte por variáveis locais, que libertam a memória assim que a função termina a sua execução, sendo mais eficientes em termos de gestão de memória.
As variáveis globais assemelham-se ao seguinte exemplo, mas use-as apenas quando necessário:
int x = 5 // Variável global void func(){ print(x) }
Em alternativa, pode utilizar variáveis locais como no exemplo seguinte:
void func(){ int x = 5 // Variável local print(x) }
Limitar a Memória Cache
Defina um limite para a memória que a cache pode utilizar. Por vezes, todas as tarefas executadas no sistema são armazenadas na memória cache, e esta acumulação pode resultar em fugas de memória.
Portanto, limitar a cache pode prevenir o surgimento de fugas de memória.
Testar Adequadamente
Inclua testes de fugas de memória na fase de testes do seu projeto.
Crie testes automatizados e cubra todos os casos extremos para detetar fugas de memória antes de lançar o código em produção.
Utilizar Ferramentas de Monitorização
Recorra a ferramentas automáticas para monitorizar o uso da memória. O acompanhamento regular da utilização da memória ajuda a identificar potenciais fugas e a corrigi-las numa fase inicial.
O Visual Studio Profiler, o NET Memory Profiler e o JProfiler são algumas das melhores ferramentas neste contexto.
Conclusão
Uma gestão eficiente da memória é essencial para atingir o desempenho máximo de uma aplicação e as fugas de memória não podem ser ignoradas neste contexto. Para uma gestão de memória eficaz, deve lidar com as fugas de memória e evitar que estas ocorram no futuro. Este artigo é dedicado à forma como pode alcançar este objetivo.
Apresentamos vários métodos para detetar fugas de memória, etapas comprovadas para as corrigir e práticas a adotar para evitar futuras fugas de memória.
Pode também consultar informações adicionais sobre como corrigir o erro de “falta de memória” no Windows.