que - vazamento de recursos java




Criando um vazamento de memória com Java (20)

A maioria dos exemplos aqui são "muito complexos". Eles são casos de ponta. Com esses exemplos, o programador cometeu um erro (como não redefinir equals / hashcode), ou foi mordido por um caso de canto da JVM / JAVA (carga de classe com static ...). Acho que esse não é o tipo de exemplo que um entrevistador quer ou até mesmo o caso mais comum.

Mas existem casos mais simples para vazamentos de memória. O coletor de lixo só libera o que não é mais referenciado. Nós, como desenvolvedores Java, não nos importamos com a memória. Nós alocamos quando necessário e deixamos que seja liberado automaticamente. Bem.

Mas qualquer aplicativo de longa duração tende a ter estado compartilhado. Pode ser qualquer coisa, estática, singletons ... Muitas vezes aplicações não-triviais tendem a fazer gráficos de objetos complexos. Apenas esquecer de definir uma referência para null ou, mais freqüentemente, esquecer de remover um objeto de uma coleção é suficiente para fazer um vazamento de memória.

É claro que todo tipo de ouvintes (como ouvintes da interface do usuário), caches ou qualquer estado compartilhado de longa duração tendem a produzir vazamento de memória se não forem manipulados adequadamente. O que deve ser entendido é que este não é um caso de canto de Java ou um problema com o coletor de lixo. É um problema de design. Nós projetamos que adicionamos um ouvinte a um objeto de longa duração, mas não removemos o ouvinte quando não é mais necessário. Nós armazenamos objetos em cache, mas não temos nenhuma estratégia para removê-los do cache.

Talvez tenhamos um gráfico complexo que armazene o estado anterior que é necessário por uma computação. Mas o estado anterior está ele próprio ligado ao estado antes e assim por diante.

Como se tivéssemos de fechar conexões ou arquivos SQL. Precisamos definir referências apropriadas para nulo e remover elementos da coleção. Devemos ter estratégias de cache adequadas (tamanho máximo de memória, número de elementos ou temporizadores). Todos os objetos que permitem que um ouvinte seja notificado devem fornecer um método addListener e removeListener. E quando esses notificadores não são mais usados, eles devem limpar sua lista de ouvintes.

Um vazamento de memória é realmente possível e é perfeitamente previsível. Não há necessidade de recursos especiais de idioma ou casos de canto. Vazamentos de memória são um indicador de que algo está faltando ou até mesmo de problemas de design.

Acabei de fazer uma entrevista, e me pediram para criar um vazamento de memória com o Java. Escusado será dizer que me senti muito burro não ter idéia de como começar a criar um.

Como seria um exemplo?


A resposta depende inteiramente do que o entrevistador pensou que eles estavam perguntando.

É possível na prática fazer vazamento de Java? Claro que é, e há muitos exemplos nas outras respostas.

Mas existem múltiplas meta-perguntas que podem ter sido feitas?

  • Uma implementação Java teoricamente "perfeita" é vulnerável a vazamentos?
  • O candidato compreende a diferença entre teoria e realidade?
  • O candidato entende como a coleta de lixo funciona?
  • Ou como a coleta de lixo deve funcionar em um caso ideal?
  • Eles sabem que podem chamar outras linguagens por meio de interfaces nativas?
  • Eles sabem que vazam memória nesses outros idiomas?
  • O candidato sabe mesmo o que é gerenciamento de memória e o que está acontecendo por trás do cenário em Java?

Estou lendo sua meta pergunta como "Qual é a resposta que eu poderia ter usado nesta situação de entrevista". E, portanto, vou me concentrar em habilidades de entrevista em vez de Java. Eu acredito que é mais provável que você repita a situação de não saber a resposta a uma pergunta em uma entrevista do que você esteja em um lugar de precisar saber como fazer o vazamento de Java. Então, espero que isso ajude.

Uma das habilidades mais importantes que você pode desenvolver para a entrevista é aprender a escutar ativamente as perguntas e trabalhar com o entrevistador para extrair sua intenção. Isso não apenas permite que você responda a sua pergunta da maneira que deseja, mas também mostra que você tem algumas habilidades de comunicação vitais. E quando se trata de uma escolha entre muitos desenvolvedores igualmente talentosos, eu contratarei quem ouve, pensa e entende antes de responder todas as vezes.


Aqui está uma boa maneira de criar um verdadeiro vazamento de memória (objetos inacessíveis executando código, mas ainda armazenados na memória) em Java puro:

  1. O aplicativo cria um thread de longa duração (ou usa um pool de threads para vazar ainda mais rápido).
  2. O encadeamento carrega uma classe através de um ClassLoader (opcionalmente personalizado).
  3. A classe aloca um grande pedaço de memória (por exemplo, new byte[1000000] ), armazena uma forte referência a ele em um campo estático e, em seguida, armazena uma referência para si mesmo em um ThreadLocal. A alocação da memória extra é opcional (o vazamento da instância da classe é suficiente), mas fará com que o vazamento funcione muito mais rápido.
  4. O thread limpa todas as referências à classe personalizada ou ao ClassLoader do qual foi carregada.
  5. Repetir.

Isso funciona porque o ThreadLocal mantém uma referência ao objeto, que mantém uma referência a sua classe, que por sua vez mantém uma referência ao seu ClassLoader. O ClassLoader, por sua vez, mantém uma referência a todas as Classes que ele carregou.

(Foi pior em muitas implementações de JVM, especialmente antes do Java 7, porque Classes e ClassLoaders foram alocados diretamente para o permgen e nunca foram GC'd. Entretanto, independentemente de como a JVM manipula o descarregamento de classes, um ThreadLocal ainda evitará Objeto de classe de ser recuperado.)

Uma variação desse padrão é por que os contêineres de aplicativos (como o Tomcat) podem vazar memória como uma peneira se você reimplementar com frequência os aplicativos que usam o ThreadLocals de alguma forma. (Como o contêiner de aplicativos usa Threads, conforme descrito, e toda vez que você reimplanta o aplicativo, um novo ClassLoader é usado.)

Atualização : como muitas pessoas continuam perguntando, veja alguns exemplos de código que mostram esse comportamento em ação .


Eu posso copiar minha resposta daqui: maneira mais fácil de causar vazamento de memória em Java?

"Um vazamento de memória, em ciência da computação (ou vazamento, neste contexto), ocorre quando um programa de computador consome memória, mas não consegue liberá-lo de volta ao sistema operacional." (Wikipedia)

A resposta fácil é: você não pode. Java faz gerenciamento automático de memória e libera recursos que não são necessários para você. Você não pode impedir que isso aconteça. Será sempre capaz de liberar os recursos. Em programas com gerenciamento de memória manual, isso é diferente. Você não pode obter alguma memória em C usando malloc (). Para liberar a memória, você precisa do ponteiro que malloc retornou e chamar free (). Mas se você não tiver mais o ponteiro (sobrescrito ou a vida útil excedida), infelizmente, você será incapaz de liberar essa memória e, portanto, terá um vazamento de memória.

Todas as outras respostas até agora estão na minha definição, não realmente vazamentos de memória. Todos eles visam preencher a memória com coisas sem sentido muito rápido. Mas a qualquer momento você ainda pode desreferenciar os objetos que criou e, assim, liberar a memória -> SEM VAZAMENTO. A resposta de Acconrad chega bem perto, como eu tenho que admitir, já que sua solução é efetivamente apenas "travar" o coletor de lixo, forçando-o em um loop infinito).

A resposta longa é: Você pode obter um vazamento de memória escrevendo uma biblioteca para Java usando o JNI, que pode ter gerenciamento de memória manual e, portanto, ter vazamentos de memória. Se você chamar essa biblioteca, seu processo java irá vazar memória. Ou você pode ter erros na JVM, para que a JVM perca memória. Provavelmente existem erros na JVM, pode até haver alguns conhecidos, já que a coleta de lixo não é tão trivial, mas ainda assim é um bug. Por design, isso não é possível. Você pode estar pedindo algum código java que é afetado por esse bug. Desculpe eu não conheço um e pode não ser mais um bug na próxima versão do Java.


Provavelmente, um dos exemplos mais simples de um possível vazamento de memória e como evitá-lo é a implementação de ArrayList.remove (int):

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

Se você estivesse implementando você mesmo, você teria pensado em limpar o elemento da matriz que não é mais usado ( elementData[--size] = null )? Essa referência pode manter um objeto enorme vivo ...


Toda vez que você mantiver referências a objetos que não precisa mais, terá um vazamento de memória. Consulte Manipulando Vazamentos de Memória em Programas Java para exemplos de como vazamentos de memória se manifestam em Java e o que você pode fazer a respeito.


Você é capaz de fazer vazamento de memória com a classe sun.misc.Unsafe . Na verdade, essa classe de serviço é usada em classes padrão diferentes (por exemplo, em classes java.nio ). Você não pode criar uma instância dessa classe diretamente , mas você pode usar a reflexão para fazer isso .

O código não compila no Eclipse IDE - compile-o usando o comando javac (durante a compilação você receberá avisos)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;


public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }

}

Campo estático que contém a referência do objeto [campo final da esp]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

Chamando String.intern() na cadeia longa

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();

(Unclosed) open streams (arquivo, rede etc ...)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Conexões não fechadas

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Áreas que estão inacessíveis do coletor de lixo da JVM , como memória alocada por meio de métodos nativos

Em aplicativos da Web, alguns objetos são armazenados no escopo do aplicativo até que o aplicativo seja explicitamente interrompido ou removido.

getServletContext().setAttribute("SOME_MAP", map);

Opções de JVM incorretas ou inadequadas , como a opção noclassgc no IBM JDK que impede a coleta de lixo de classe não utilizada

Veja as configurações do IBM jdk .


O entrevistador pode estar procurando uma solução de referência circular:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

Esse é um problema clássico com coletores de lixo de contagem de referência. Você explicaria educadamente que as JVMs usam um algoritmo muito mais sofisticado que não tem essa limitação.

-Wes Tarle


O que é um vazamento de memória?

  • É causado por um bug ou design ruim.
  • É um desperdício de memória.
  • Isso piora com o tempo.
  • O coletor de lixo não pode limpá-lo.

Exemplo típico:

Um cache de objetos é um bom ponto de partida para atrapalhar as coisas.

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

Seu cache cresce e cresce. E logo o banco de dados inteiro é sugado para a memória. Um design melhor usa um LRUMap (Mantém apenas objetos usados ​​recentemente no cache).

Claro, você pode tornar as coisas muito mais complicadas:

  • usando construções ThreadLocal .
  • adicionando árvores de referência mais complexas .
  • ou vazamentos causados ​​por bibliotecas de terceiros .

O que acontece frequentemente

Se este objeto Info tiver referências a outros objetos, os quais terão novamente referências a outros objetos. De certa forma, você também pode considerar isso como um vazamento de memória (causado por um design ruim).


Talvez usando código nativo externo através do JNI?

Com Java puro, é quase impossível.

Mas isso é sobre um tipo "padrão" de vazamento de memória, quando você não pode mais acessar a memória, mas ainda é propriedade do aplicativo. Em vez disso, você pode manter referências a objetos não utilizados ou abrir fluxos sem fechá-los posteriormente.


Achei interessante que ninguém usasse os exemplos de classes internas. Se você tem uma aula interna; inerentemente mantém uma referência à classe de contenção. Claro que tecnicamente não é um vazamento de memória porque o Java WILL eventualmente irá limpá-lo; mas isso pode fazer com que as classes permaneçam mais tempo do que o esperado.

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Agora, se você chamar Example1 e obter um Example2 descartando Example1, você inerentemente ainda terá um link para um objeto Example1.

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

Eu também ouvi um boato de que se você tem uma variável que existe por mais tempo do que uma quantidade específica de tempo; Java supõe que sempre existirá e nunca tentará limpá-lo se não puder mais ser alcançado no código. Mas isso é completamente não verificado.


Como muitas pessoas sugeriram, vazamentos de recursos são bastante fáceis de causar - como os exemplos do JDBC. Vazamentos de memória reais são um pouco mais difíceis - especialmente se você não estiver contando com bits quebrados da JVM para fazer isso por você ...

As idéias de criar objetos que têm uma pegada muito grande e, em seguida, não serem capazes de acessá-los, também não são vazamentos reais de memória. Se nada puder acessá-lo, então será coletado lixo, e se algo puder acessá-lo, então não é um vazamento ...

Uma maneira que costumava funcionar embora - e eu não sei se ainda existe - é ter uma cadeia circular de três profundidades. Como no Objeto A tem uma referência ao Objeto B, o Objeto B tem uma referência ao Objeto C e o Objeto C tem uma referência ao Objeto A. O GC foi inteligente o bastante para saber que uma cadeia profunda dois - como em A <-> B - pode ser coletado com segurança se A e B não puderem ser acessados ​​por outra coisa, mas não puderem lidar com a corrente de três vias ...


Crie um mapa estático e continue a adicionar referências concretas a ele. Aqueles nunca serão GC'd.

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}

Eu não acho que alguém tenha dito isso ainda: você pode ressuscitar um objeto substituindo o método finalize () de forma que finalize () armazene uma referência disso em algum lugar. O coletor de lixo só será chamado uma vez no objeto, de forma que o objeto nunca seja destruído.


Eu tive um bom "vazamento de memória" em relação à análise PermGen e XML uma vez. O analisador XML que usamos (não me lembro qual deles foi) fez um String.intern () nos nomes das tags, para tornar a comparação mais rápida. Um de nossos clientes teve a ótima ideia de armazenar valores de dados não em atributos XML ou texto, mas como tagnames, então tivemos um documento como:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

De fato, eles não usaram números, mas identificações textuais mais longas (cerca de 20 caracteres), que eram únicos e chegavam a uma taxa de 10 a 15 milhões por dia. Isso faz com que 200 MB de lixo por dia, o que nunca é necessário novamente, e nunca GCed (já que está em PermGen). Nós tivemos permgen definido para 512 MB, então demorou cerca de dois dias para a exceção de falta de memória (OOME) chegar ...


O entrevistador provavelmente estava procurando por uma referência circular como o código abaixo (que, aliás, apenas vaza memória em JVMs muito antigas que usam a contagem de referência, o que não é mais o caso). Mas é uma questão bem vaga, então é uma excelente oportunidade para mostrar sua compreensão do gerenciamento de memória da JVM.

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

Então você pode explicar que com a contagem de referência, o código acima vazaria memória. Mas a maioria das JVMs modernas não usa mais a contagem de referências, a maioria usa um coletor de lixo de varredura, que de fato coleta essa memória.

Em seguida, você pode explicar a criação de um objeto que tenha um recurso nativo subjacente, como este:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

Então você pode explicar que isso é tecnicamente um vazamento de memória, mas realmente o vazamento é causado pelo código nativo na JVM alocando recursos nativos subjacentes, que não foram liberados pelo seu código Java.

No final do dia, com uma JVM moderna, você precisa escrever algum código Java que aloque um recurso nativo fora do escopo normal do reconhecimento da JVM.


Pegue qualquer aplicativo da web em execução em qualquer contêiner de servlet (Tomcat, Jetty, Glassfish, o que for ...). Reimplante o aplicativo 10 ou 20 vezes seguidas (pode ser o suficiente simplesmente tocar no WAR no diretório autodeploy do servidor.

A menos que alguém tenha realmente testado isso, há grandes chances de que você receba um OutOfMemoryError após algumas reimplementações, porque o aplicativo não tomou o cuidado de limpar depois de si mesmo. Você pode até encontrar um bug no seu servidor com este teste.

O problema é que a vida útil do contêiner é maior que a vida útil do seu aplicativo. Você precisa garantir que todas as referências que o contêiner possa ter para objetos ou classes de seu aplicativo possam ser coletadas como lixo.

Se houver apenas uma referência sobrevivendo à falta de emprego do seu aplicativo da Web, o carregador de classe correspondente e, por consequência, todas as classes do seu aplicativo da Web não poderão ser coletadas como lixo.

Threads iniciados pelo seu aplicativo, variáveis ​​ThreadLocal, anexadores de registro são alguns dos suspeitos usuais para causar vazamentos no classloader.


Todos sempre esquecem a rota do código nativo. Aqui está uma fórmula simples para um vazamento:

  1. Declare o método nativo.
  2. No método nativo, ligue malloc. Não ligue free.
  3. Chame o método nativo.

Lembre-se de que as alocações de memória no código nativo vêm do heap da JVM.


Um exemplo comum disso no código da GUI é ao criar um widget / componente e incluir um ouvinte em um objeto com escopo estático / aplicativo e, em seguida, não remover o ouvinte quando o widget é destruído. Não só você recebe um vazamento de memória, mas também um impacto de desempenho como quando tudo o que você está ouvindo dispara eventos, todos os seus antigos ouvintes são chamados também.





memory-leaks