TransWikia.com

O coletor de lixo existe mesmo? Por que então há vazamento de memória em runtimes que utilizam-no?

Stack Overflow em Português Asked by Marcelo Shiniti Uchimura on December 11, 2021

Eu já ouvi falar de vazamento de memória no Oracle.DataAccess.dll e em aplicações de grande porte desenvolvidas para runtimes com coletor de lixo.

Se o coletor de lixo existe nesses runtimes, por que há vazamento de memória?

2 Answers

Coletores de lixo coletam lixo, e não outras coisas. Qualquer objeto no heap que tem uma referência para ele não é lixo, não tem porque coletar.

Não é tão simples responder a pergunta especificamente porque não um problema específico, mas é fácil responder de forma generalizada, e por isso não importa se é no CLR ou na JVM.

Definir o que é vazamento de memória é complicado. Se considerar que objetos não são destruídos após eles não serem mais referenciados, vaza o tempo todo... temporariamente. Não pode é vazar em definitivo, por isso até mesmo um objeto que sobreviva durante toda a execução da aplicação pode não ser um vazamento se ele está em uso, mesmo que inadvertidamente.

E isso é importante, o GC funciona quando o código está fazendo o certo. O GC é sobre gerenciar automaticamente a memória, não é sobre fazer tudo certo no código. Se deixa algo em uso que não deveria, não está vazando memória, o erro é outro. Vazamento, pra mim, é quando a intenção é liberar a memória e não o faz.

Tem várias situações que são complicados de gerenciar.

Note que o garbage colector só libera memória de objetos gerenciados por ele. Então qualquer coisa que seja alocada sem o seu uso, e é possível fazer isto na JVM, mas atualmente não diretamente no Java, e no CLR até mesmo pelo C# em contexto unsafe, pode vazar nestes casos se o tratamento manual da memória não for bem feito.

Também podemos falar dos recursos não gerenciados pela aplicação, como um arquivo ou banco de dados. Embora eles ocupem memória da aplicação Java ou .NET não é memória gerenciada diretamente. Se o recurso não for liberado pela sua aplicação pelo padrão de projeto Dispose ou totalmente manual, vazará.

Tem casos que podem ocorrer vazamento pela forma como se implementa algum padrão. Um exemplo são os eventos que se você não remover os assinantes corretamente pode manter referência para um objeto que não é mais necessário, o que impede sua remoção mesmo estando sem função.

Enganos

A resposta (citada em comentário pelo hkotsubo) do SO é ruim. Primeiro porque ignora o problema indecidível e segundo porque fala em vazamento de memória algo que torna tudo no heap um vazamento de memória conforme eu falei, mesmo assim a resposta não trata do problema pela definição que forneceu.

Dependendo da definição, não é vazamento de memória se tem uma referência para o objeto, é um erro de programação que não permite a liberação da memória porque o objeto está em uso. Claro, tem definição que considera isso como vazamento. Não sei se o exemplo do Victor é uma vazamento porque pode ser a intenção. O objeto está em uso, não vejo erro algum, mas se tiver é de intenção, não de programação. O objeto fica até estar na lista, e pode sair a qualquer hora. De forma geral eu diria que não é um vazamento.

Exemplo específico

Eu não ouvi falar de vazamento no Oracle.DataAccess.dll, o que é algo do .NET e não JVM e deve ser um erro programação de quem fez esse código dentro das possibilidades que eu citei, ou é um erro de uso das classes deste módulo que não permite a liberação, aí o erro não é dele e sim de quem usa ele.

Pra responder ao Jefferson Quesado (comentário):

Um dos motivos de existir o tracing garbage colector é justamente evitar referências cíclicas impedirem a liberação. O algoritmo dos coletores rastreadores verificam quais são as referências ativas, em geral começam pelas raízes que são as referências contidas nos registradores e na pilha, eventualmente em área estática se isto for possível na linguagem/ambiente, depois vai pro heap que é onde o GC atua.

O GC da JVM e do CLR é compactador e geracional. Isso significa que ele não remove lixo efetivamente, ele apenas preserva o que ainda está vivo, portanto se tem uma referência para ele, o objeto será copiado para outra geração. Os objetos que não são identificados como tendo pelo menos uma referência são abandonados em área que será reaproveitada depois, ou liberada (geralmente não).

Neste exemplo do Victor se o método está em execução durante a coleta obviamente que tem uma referência na a pilha, então a lista será preservada. Se o método não está mais em execução, não tem uma referência para a lista, portanto ela não será copiada para a próxima geração e no final da coleta a área onde ela estava será liberada para uso, o que na prática é uma destruição.

Se a lista não é referenciada nas raízes ou em outras estruturas no heap que referenciam a ela, não tem porque sequer verificar se ela tem referências para outros objetos, seja autorreferenciados ou não. O objeto que está dentro dela não é avaliado para saber se é uma referência para outra coisa, muito menos se é para ela mesma.

Claro que se identificar que em algum outro lugar tem uma referência para a lista, ela será copiada e aí quando ver que o objeto, cujo alias temporário neste exemplo é o x, ela saberá que a lista precisa ser copiada, mas ele já sabia antes. Dizer que o objeto tem uma, duas ou mil referências para ele dá na mesma, só uma cópia será feita (tecnicamente é um move porque o original será abandonado), as referências serão atualizadas para o novo endereço do objeto copiado para a nova geração.

Entenda que um objeto que está dentro de uma lista ou outra estrutura pode ser copiado individualmente se tiver uma referência para ele em algum outro lugar, isto não quer dizer que a lista precise ser copiada. Salvo engano, o Java não permite isto, não pode haver referência para referências dentro de uma estrutura, o C# sim.

Lembrando que mecanismo de contagem de referência (reference count) é um GC e ele permite vazamento por referências circulares, a não ser em um mecanismo muito sofisticado com problemas próprios que chega ao ponto de precisar de um tracing GC de backup ou mesmo a mudança por completo para os casos que podem ter referências circulares, especialmente as que não são diretas e em grafos, que são bem complicadas de serem detectadas, quando é possível.

Answered by Maniero on December 11, 2021

Por que o coletor de lixo só coleta referências que não são mais acessíveis. No entanto, ainda é possível haver vazamentos de memória por meio de referências acessíveis. Por exemplo:

 public class VazadorDeMemoria {
     private static final List<Object> monteDeLixo = new ArrayList<>();

     public static void fazerComQueObjetoNuncaPossaSerColetado(Object x) {
          monteDeLixo.add(x);
     }
 }

Qualquer objeto que seja passado como parâmetro a esse método aí, ficará preso na memória indefinidamente e nunca poderá ser coletado como lixo. E com ele, todos os demais objetos ao qual ele se refere.

Esse caso aí até que é bastante óbvio. No entanto, existem diversas formas sutis e nada óbvias de se atingir resultados semelhantes que podem surgir acidentalmente quando se está programando. O que ocorre é que de alguma forma, o lixo é mantido na memória mas ainda existindo referências acessíveis apontando para ele, e portanto não podendo ser coletado.

Answered by Victor Stafusa on December 11, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP