Criar uma fuga de memória com Java
30 answers
Aqui está uma boa maneira de criar uma verdadeira fuga de memória (objectos inacessíveis através da execução de código, mas ainda armazenados na memória) em Java puro:
- a aplicação cria um fio de longa duração (ou usa um conjunto de fios para vazar ainda mais rápido).
- o tópico carrega uma classe através de um carregador de classes (opcionalmente personalizado).
- A classe aloca um grande pedaço de memória (por exemplo
new byte[1000000]
), armazena uma forte referência a ela em um campo estático, e então armazena uma referência a si mesma em um ThreadLocal. A alocação da memória extra é opcional (vazar a instância da classe é suficiente), mas fará com que a fuga funcione muito mais rápido. - o tópico limpa todas as referências à classe personalizada ou ao carregador de classes de onde foi carregado. Repito.
(foi pior em muitas implementações JVM, especialmente antes do Java 7, porque Classes e ClassLoaders foram alocados diretamente em permgen e nunca foram GC'D em tudo. No entanto, independentemente de como a JVM lida com a descarga de classe, Um ThreadLocal ainda vai impedir que um objeto de classe seja recuperado.)
Uma variação neste padrão é a razão pela qual os recipientes de aplicação (como o Tomcat) podem vazar memória como um peneiro se você frequentemente recolocar aplicações que acontecem de usar De qualquer forma, os três locais. (Uma vez que o recipiente de aplicação usa Threads como descrito, e cada vez que você recolocar a aplicação um novo carregador de classe é usado.)
Update : dado que muitas pessoas continuam a perguntar por ele, aqui está um código de exemplo que mostra este comportamento em acção .
Referência de objecto imobilizado em campo estático [campo final esp]
class MemorableClass {
static final ArrayList list = new ArrayList(100);
}
A chamar String.intern()
num texto longo
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();
(não fechado) fluxos abertos (ficheiro , rede, etc... )
try {
BufferedReader br = new BufferedReader(new FileReader(inputFile));
...
...
} catch (Exception e) {
e.printStacktrace();
}
Ligações não fechadas
try {
Connection conn = ConnectionFactory.getConnection();
...
...
} catch (Exception e) {
e.printStacktrace();
}
Áreas inacessíveis ao coletor de lixo da JVM , tais como a memória alocada através de métodos nativos
Em aplicações web, alguns objectos são armazenados no âmbito de Aplicação até que a aplicação seja explicitamente interrompida ou removida.
getServletContext().setAttribute("SOME_MAP", map);
Opções JVM incorrectas ou inapropriadas, tais como a opção noclassgc
na IBM JDK que impede a recolha de lixo da classe não utilizada
Ver IBM JDK settings .
Uma coisa simples a fazer é usar uma HashSet com um incorrecto (ou inexistente) hashCode()
ou equals()
, e depois continuar a adicionar "duplicados". Em vez de ignorar duplicados como deveria, o conjunto só vai crescer e você não será capaz de removê-los.
Se quiser que estas teclas/elementos ruins fiquem por aí, pode usar um campo estático como
class BadKey {
// no hashCode or equals();
public final String key;
public BadKey(String key) { this.key = key; }
}
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
Abaixo haverá um caso não-óbvio onde o Java vaza, além do caso padrão de ouvintes esquecidos, referências estáticas, chaves fictícias/modificáveis em hashmaps, ou apenas threads presos sem qualquer chance de terminar seu ciclo de vida.
-
{[[0]} - sempre vaza a corda,
se a corda é uma substring, a fuga é ainda pior (o char subjacente[] também vazou)- no Java 7 substring também copia ochar[]
, de modo que o posterior não se aplica ; @Daniel, não mas precisa de votos.
Runtime.addShutdownHook
e não remover... e então mesmo com removeShutdownHook devido a um bug na classe ThreadGroup em relação a threadgroup unstarted threads ele pode não ser coletado, efetivamente vazar o ThreadGroup. O JGroup tem a fuga no Coscuvilheiro.Criando, mas não começando, um
Thread
vai para a mesma categoria que acima.A Criação de um thread herda o
ContextClassLoader
eAccessControlContext
, mais oThreadGroup
e qualquerInheritedThreadLocal
, todas essas referências são potenciais vazamentos, além de toda a classes carregado pelo carregador de classe e todas as referências estáticas, e ja-ja. O efeito é especialmente visível com toda a estrutura Executor J.u.c. que possui uma interface super simplesThreadFactory
, mas a maioria dos desenvolvedores não tem nenhuma pista do perigo que espreita. Também muitas bibliotecas começam threads a pedido (muitas indústrias bibliotecas populares).ThreadLocal
caches, esses são maus em muitos casos. Estou certo de que todos viram um pouco de caches simples baseados em ThreadLocal, bem a má notícia: se o fio continua indo mais do que o esperado a vida o ClassLoader de contexto, é um puro pequeno vazamento agradável. Não utilize bigodes Threadlocais a menos que seja realmente necessário.Chamando
ThreadGroup.destroy()
quando o grupo de três não tem nenhum tópico em si, mas ainda mantém grupos de três filhos. Uma má fuga que evitará o grupo de três para remover de seu pai, mas todas as crianças tornam-se in-enumerável.Usando WeakHashMap e o valor (in)diretamente referencia a chave. Esta é difícil de encontrar sem uma lixeira. Isso se aplica a todos os estendidos
Weak/SoftReference
que podem manter uma referência dura de volta ao objeto guardado.Usando
java.net.URL
com o(S) protocolo (s) HTTP e carregando o recurso de (!). Este é especial, oKeepAliveCache
cria um novo tópico no grupo de três sistemas que vaza o leitor de contexto do tópico actual. O fio é criado após o primeiro pedido quando não existe fio vivo, então você pode ter sorte ou apenas vazar. A fuga já está fixada no Java 7 e o código que cria o thread correctamente remove o leitor de classes de contexto. existem poucos mais casos (como o ImageFetcher, também fixo ) de criar tópicos semelhantes.Usando
InflaterInputStream
a passarnew java.util.zip.Inflater()
no construtor (PNGImageDecoder
por exemplo) e não ligar ao insuflador. Bem, Se passares no construtor com apenas [[18]}, nem pensar... E sim, chamarclose()
no fluxo não fecha o insuflador se for passado manualmente como parâmetro do construtor. Isto não é uma verdadeira fuga, uma vez que seria lançada pelo finalizador... quando o considerar necessário. Até aquele momento ele come tanto a memória nativa que pode causar Linux oom_killer para matar o processo com impunidade. A questão principal é que a finalização em Java é muito pouco confiável e O G1 piorou até às 7h02. Moral da história: libere recursos nativos o mais rápido possível; o finalizador é muito pobre.O mesmo caso com
java.util.zip.Deflater
. Este é muito pior, já que Deflater tem fome de memória em Java, ou seja, usa sempre 15 bits (max) e 8 níveis de memória (9 é max) alocando várias centenas de KB de memória nativa. Felizmente,Deflater
não é amplamente utilizado e, tanto quanto sei, o JDK não contém erros. Ligue sempreend()
se criar manualmente umDeflater
ouInflater
. A melhor parte dos dois últimos: Você não pode encontrá-los através de ferramentas normais de análise de perfis disponíveis.
(posso acrescentar mais alguns perdedores de tempo que encontrei a pedido.)
Boa sorte e fica a salvo, as fugas são más!Mas qualquer aplicação de longa duração tende a ter estado partilhado. 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 a nulo ou mais frequentemente esquecer de remover um objeto de uma coleção é suficiente para fazer uma fuga de memória.
Claro que todos os tipos de ouvintes (como ouvintes UI), baratas, ou qualquer compartilhamento de longa duração estado tendem a produzir vazamento de memória, se não manuseados corretamente. O que deve ser entendido é que este não é um caso de Canto 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 vida, mas não removemos o ouvinte quando já não precisamos. Nós armazenamos objetos, mas não temos estratégia para removê-los do cache.
Talvez tenhamos um gráfico complexo que armazena o estado anterior que é necessário por uma computação. Mas o anterior o estado Está ligado ao estado antes e assim por diante.
Como se tivéssemos de fechar ligações SQL ou ficheiros. Precisamos definir referências adequadas para nulos e remover elementos da coleção. Teremos estratégias de cache adequadas (tamanho máximo da memória, número de elementos ou temporizadores). Todos os objetos que permitem a notificação de um ouvinte devem fornecer tanto um método addlistener quanto um removeListener. E quando estes Notificadores deixarem de ser utilizados, terão de limpar a sua lista de ouvintes. Uma memória a fuga é realmente possível e perfeitamente previsível. Não há necessidade de recursos linguísticos especiais ou casos de canto. Vazamentos de memória são ou um indicador de que algo está faltando ou até mesmo de problemas de design.- É uma implementação Java teoricamente "perfeita" vulnerável a fugas?
O CANDIDATO compreende a diferença entre teoria e realidade?
- O o candidato sabe como funciona a recolha do lixo? Ou como é que a recolha de lixo funciona num caso ideal? Eles sabem que podem ligar para outras línguas através de interfaces nativas? Eles sabem vazar memória nas outras línguas? O candidato sabe sequer o que é a gestão da memória e o que se passa por trás da cena em Java?
O seguinte é um exemplo bastante inútil, se você não entender JDBC. Ou pelo menos como o JDBC espera que um desenvolvedor feche Connection
, Statement
e ResultSet
instâncias antes de descartá-las ou perder referências a elas, em vez de confiar na implementação de finalize
.
void doWork()
{
try
{
Connection conn = ConnectionFactory.getConnection();
PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
ResultSet rs = stmt.executeQuery();
while(rs.hasNext())
{
... process the result set
}
}
catch(SQLException sqlEx)
{
log(sqlEx);
}
}
O problema com o acima é que o objeto Connection
não está fechado, e, portanto, a conexão física permanecerá aberta, até que o coletor de lixo venha e veja que é inacessivel. O GC irá invocar o método finalize
, mas existem drivers JDBC que não implementam o finalize
, pelo menos não da mesma forma que o Connection.close
é implementado. O comportamento resultante é que enquanto a memória será recuperada devido a objetos não alcançáveis sendo coletados, recursos (incluindo memória) associados com o objeto Connection
podem simplesmente não ser recuperados.
Num evento em que o método Connection
'S finalize
não limpa tudo, pode-se realmente descobrir que o método físico a conexão com o servidor de banco de dados irá durar vários ciclos de coleta de lixo, até que o servidor de banco de dados finalmente descobre que a conexão não está viva (se ele faz), e deve ser fechada.
finalize
é garantido ser invocado apenas uma vez.
A o cenário acima de encontrar exceções durante a finalização do objeto está relacionado a outro cenário que poderia possivelmente levar a uma fuga de memória-ressurreição do objeto. A ressurreição do objeto é muitas vezes feita intencionalmente, criando uma forte referência ao objeto de ser finalizado, a partir de outro objeto. Quando a ressurreição do objeto é usurpada, ela levará a um vazamento de memória em combinação com outras fontes de vazamento de memória.
Existem muitos mais exemplos que você pode evocar - tipo
- gerir uma instância
List
onde só está a adicionar à lista e não a apagar dela( embora devesse estar a livrar-se dos elementos de que já não necessita), ou - Abrir
Socket
S ouFile
S, mas não fechá-los quando já não são necessários (semelhante ao exemplo acima envolvendo a classeConnection
).
Não descarregue Singletons quando derruba uma aplicação Java EE. Aparentemente, o carregador de classe que carregou a classe singleton vai manter um referência à classe, e, portanto, a instância singleton nunca será coletada. Quando uma nova instância da aplicação é implantada, um novo carregador de classe é geralmente criado, e o carregador de classe anterior continuará a existir devido ao singleton.
Provavelmente um dos exemplos mais simples de um potencial vazamento de memória, e como evitá-lo, é a implementação do ArrayList.remover (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 estivesse a implementá-lo, teria pensado em Limpar o elemento array que já não é usado (elementData[--size] = null
)? Essa referência pode manter um objecto enorme vivo ...
O código não compila no IDE do Eclipse-compila-o usando o comando {[[1]} (durante a compilação irá obter 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();
}
}
}
" 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 é incapaz de liberá-la de volta para o sistema operacional."(Wikipedia)
A resposta fácil é: você não pode. Java faz gestão automática de memória e irá libertar recursos que não são necessários para você. Não podes impedir que isto aconteça. Ele será sempre capaz de liberar o recurso. Em programas com gerenciamento de memória manual, isso é diferente. Você não pode obter alguma memória em C usando malloc(). Para libertar a memória, você precisa do ponteiro que malloc devolveu e chamar livre() sobre ele. Mas se você não tem mais o ponteiro (substituído, ou ultrapassado a vida), então você é, infelizmente, incapaz de libertar esta memória e, assim, você tem uma fuga de memória.
Todas as outras respostas até agora estão na minha definição, não são fugas de memória. Todos eles visam encher a memória com coisas inúteis muito depressa. Mas a qualquer momento você ainda pode dereferenciar os objetos que criou e assim libertar a memória -- > nenhuma fuga. a resposta de acconrad {[[3]} chega muito perto, embora como eu tenho que admitir uma vez que a sua solução é efetivamente apenas "bater" o coletor de lixo, forçando-o em um loop interminável).A resposta longa é: você pode obter uma fuga de memória escrevendo uma biblioteca para Java usando a JNI, que pode ter gerenciamento de memória manual e, portanto, ter vazamentos de memória. Se liga para esta biblioteca, o teu processo java vai perder memória. Ou, você pode ter bugs no JVM, para que o JVM perca a memória. Há provavelmente bugs no JVM, pode até haver alguns conhecidos já que a coleta de lixo não é tão trivial, mas então ainda é um bug. Por projeto isso não é possível. Você pode estar pedindo algum código java que é efetuado por tal bug. Desculpe, não conheço nenhum e pode muito bem não ser mais um bug na próxima versão Java.
public class StringLeaker
{
private final String muchSmallerString;
public StringLeaker()
{
// Imagine the whole Declaration of Independence here
String veryLongString = "We hold these truths to be self-evident...";
// The substring here maintains a reference to the internal char[]
// representation of the original string.
this.muchSmallerString = veryLongString.substring(0, 1);
}
}
Porque a substring se refere à representação interna da cadeia original, muito mais longa, a original permanece na memória. Assim, enquanto você tiver um StringLeaker em jogo, você tem toda a string original em memória, também, mesmo que você possa pensar que você está apenas segurando uma string de um único personagem.
A forma de evitar armazenar um indesejado a referência ao texto original é fazer algo assim:
...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...
Para maior maldade, também podes .intern()
a substring:
...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...
Fazê-lo irá manter tanto a Cadeia Longa original como a substring derivada na memória, mesmo depois de a instância de StringLeaker ter sido descartada.
Um exemplo comum disto no código da interface é quando se cria um elemento/componente e se adiciona um ouvinte a algum objecto estático/de Aplicação escopado e depois não se remove o ouvinte quando o elemento é destruído. Não só você tem uma fuga de memória, mas também um sucesso de desempenho como quando o que quer que você está ouvindo incêndios eventos, todos os seus ouvintes velhos são chamados também.
Se houver apenas uma referência sobrevivendo ao não-funcionamento do seu aplicativo web, o classloader correspondente e, consequentemente, todas as classes do seu aplicativo web não podem ser coletadas.
Tópicos iniciados pela sua aplicação, ThreadLocal variáveis, apenders de registro são alguns dos suspeitos usuais para causar vazamentos de carregadores de classe.
<data>
<1>bla</1>
<2>foo</>
...
</data>
Na verdade, eles não usavam números, mas identificações textuais mais longas( cerca de 20 caracteres), que eram únicas e chegaram a uma taxa de 10-15 milhões por dia. Isso faz 200 MB de lixo por dia, o que nunca é necessário novamente, e nunca GCed (uma vez que está em PermGen). Tínhamos o permgen ajustado para 512 MB, então levou cerca de dois dias para a exceção de falta de memória (OOME) para chegar...
Log4j tem este mecanismo chamado contexto de diagnóstico aninhado (NDC) que é um instrumento para distinguir a saída de log interleaved de diferentes fontes. A granularidade em que o NDC trabalha é threads, então ele distingue log saídas de diferentes threads separadamente.
Para guardar as marcas específicas do tópico, a classe NDC do log4j usa uma Hashtable que é tecida pelo próprio objecto do tópico (ao contrário de dizer o ID thread), e assim até que a tag NDC permanece na memória todos os objetos que pendem fora do objeto thread também permanecem na memória. Na nossa aplicação web, usamos NDC para marcar logoutputs com um id de pedido para distinguir logs de um único pedido separadamente. O recipiente que associa a tag NDC com um thread, também remove - o ao retornar a resposta de um pedido. O problema ocorreu quando, durante o processamento de um pedido, um fio infantil foi gerado, algo como o seguinte código:
pubclic class RequestProcessor {
private static final Logger logger = Logger.getLogger(RequestProcessor.class);
public void doSomething() {
....
final List<String> hugeList = new ArrayList<String>(10000);
new Thread() {
public void run() {
logger.info("Child thread spawned")
for(String s:hugeList) {
....
}
}
}.start();
}
}
Então, um contexto NDC foi associado com o fio inline que foi gerado. O objeto thread que era a chave para este contexto NDC, é o thread inline que tem o objeto hugeList pendurado fora dele. Assim, mesmo depois que o tópico terminou fazendo o que estava fazendo, a referência ao hugeList foi mantida viva pelo contexto NDC Hastable, causando assim uma fuga de memória.
public class Example1 {
public Example2 getNewExample2() {
return this.new Example2();
}
public class Example2 {
public Example2() {}
}
}
Agora se você ligar para o Example1 e obter um Example2 descartando o Example1, você inerentemente ainda terá um link para um Example1 objecto.
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 assume que ela sempre existirá e realmente nunca vai tentar limpá-la se não puder ser alcançado em código mais. Mas isso não foi verificado.
- É causada por um erroou mau design.
É um desperdício de memória.
- fica pior com o tempo.
- O coletor de lixo não pode limpá-lo.
exemplo típico:
Um cache de objectos é um bom ponto de partida para estragar 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;
}
O teu tesouro cresce e cresce. E em breve toda a base de dados será sugada para a memória. Uma melhor utilização do design um LRUMap (só mantém objectos usados recentemente na 'cache').
Claro, podes tornar as coisas muito mais complicadas.
- Usandoconstruções Threadlocais .
- adicionando maisárvores de referência complexas .
- ou fugas causadas por bibliotecas de terceiros .
O que acontece muitas vezes:
Se este objecto de informação tem referências a outros objectos, que também têm referências a outros objectos. De uma forma que você também poderia considerar isso para seja algum tipo de fuga de memória, (causada por mau design).
Crie um mapa estático e continue adicionando referências duras a ele. Esses 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); }
}
Você pode criar uma fuga de memória em movimento criando uma nova instância de uma classe no método de finalização dessa classe. Pontos de bónus se o finalizador criar várias instâncias. Aqui está um programa simples que vaza todo o heap em algum momento entre alguns segundos e alguns minutos, dependendo do seu tamanho de heap:
class Leakee {
public void check() {
if (depth > 2) {
Leaker.done();
}
}
private int depth;
public Leakee(int d) {
depth = d;
}
protected void finalize() {
new Leakee(depth + 1).check();
new Leakee(depth + 1).check();
}
}
public class Leaker {
private static boolean makeMore = true;
public static void done() {
makeMore = false;
}
public static void main(String[] args) throws InterruptedException {
// make a bunch of them until the garbage collector gets active
while (makeMore) {
new Leakee(0).check();
}
// sit back and watch the finalizers chew through memory
while (true) {
Thread.sleep(1000);
System.out.println("memory=" +
Runtime.getRuntime().freeMemory() + " / " +
Runtime.getRuntime().totalMemory());
}
}
}
O entrevistador estava provavelmente à procura de uma referência circular como o código abaixo (que, por acaso, só vaza memória em JVMs muito antigos que usaram a contagem de referência, o que já não é o caso). Mas é uma pergunta bastante vaga, por isso é uma excelente oportunidade para mostrar a sua compreensão da Gestão da 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 iria vazar memória. Mas a maioria das JVMs modernas não usam mais a contagem de referências., a maioria usa um coletor de lixo varredura, que de fato vai coletar esta memória.
Em seguida, você pode explicar a criação de um objeto que tem 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 isso é tecnicamente uma fuga de memória, mas realmente a fuga é causada pelo código nativo no JVM alocando recursos nativos subjacentes, que não foram liberados pelo seu código Java.
No final do dia, com um JVM moderno, você precisa escrever algum código Java que aloca um nativo recurso fora do âmbito normal da sensibilização da JVM.-
Declare o método nativo.
- em método nativo, chama
malloc
. Não ligues.
Chama o método nativo.
Bem, o que torna isto interessante é: desta forma, você pode vazar memória heap do processo subjacente, em vez de a partir do heap da JVM.
Só precisa de um ficheiro jar com um ficheiro dentro do qual será referenciado a partir do código Java. Quanto maior o frasco file, a memória mais rápida é alocada.
Você pode facilmente criar tal frasco com a seguinte classe:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class BigJarCreator {
public static void main(String[] args) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
zos.putNextEntry(new ZipEntry("resource.txt"));
zos.write("not too much in here".getBytes());
zos.closeEntry();
zos.putNextEntry(new ZipEntry("largeFile.out"));
for (int i=0 ; i<10000000 ; i++) {
zos.write((int) (Math.round(Math.random()*100)+20));
}
zos.closeEntry();
zos.close();
}
}
Basta colar num ficheiro chamado BigJarCreator.java, compile e execute a partir da linha de comandos:
javac BigJarCreator.java
java -cp . BigJarCreator
Et voilà: você encontra um arquivo jar na sua pasta de trabalho actual com dois ficheiros lá dentro.
Vamos criar uma segunda classe.public class MemLeak {
public static void main(String[] args) throws InterruptedException {
int ITERATIONS=100000;
for (int i=0 ; i<ITERATIONS ; i++) {
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
}
System.out.println("finished creation of streams, now waiting to be killed");
Thread.sleep(Long.MAX_VALUE);
}
}
Esta classe basicamente não faz nada, mas cria objetos InputStream não referenciados. Esses objectos serão lixo. coletado imediatamente e, portanto, não contribuir para o tamanho do heap. É importante para o nosso exemplo para carregar um recurso existente a partir de um arquivo jar, e o tamanho importa aqui!
Se tiver dúvidas, tente compilar e iniciar a classe acima, mas certifique-se de escolher um tamanho de heap decente (2 MB):
javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak
Não irá encontrar aqui um erro de OOM, uma vez que não são mantidas referências, a aplicação continuará a correr independentemente do tamanho que tenha escolhido as iterações no exemplo acima. Memoria do seu processo (visível no topo (RES/RSS) ou explorador de processo) cresce a menos que a aplicação chegue ao comando de espera. Na configuração acima, ele irá alocar cerca de 150 MB em memória.
Se quiser que a aplicação jogue pelo seguro, feche o fluxo de entrada onde foi criado:
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
E o seu processo não excederá 35 MB, independentemente da contagem de iterações.
Muito simples e surpreendente.Eu não acho que ninguém tenha dito isso ainda: você pode ressuscitar um objeto, sobrepondo o método finalize() tal que finalize() armazena uma referência disso em algum lugar. O coletor de lixo só será chamado uma vez sobre o objeto para depois que o objeto nunca será destruído.
Uma maneira que usou para trabalhar no entanto - e não sei se ainda funciona - é ter uma corrente circular de três profundidades. Como em Um Objeto possui uma referência para um Objeto B, o Objeto B tem uma referência para o Objeto C e o Objeto C tem uma referência para o Objeto A. O GC foi inteligente o suficiente para saber que um dois profunda cadeia - como em Um B - pode ser recolhidas, se A e B não são acessíveis por qualquer outra coisa, mas não conseguia lidar com o three-way cadeia...
public class ServiceFactory {
private Map<String, Service> services;
private static ServiceFactory singleton;
private ServiceFactory() {
services = new HashMap<String, Service>();
}
public static synchronized ServiceFactory getDefault() {
if (singleton == null) {
singleton = new ServiceFactory();
}
return singleton;
}
public void addService(String name, Service serv) {
services.put(name, serv);
}
public void removeService(String name) {
services.remove(name);
}
public Service getService(String name, Service serv) {
return services.get(name);
}
// the problematic api, which expose the map.
//and user can do quite a lot of thing from this api.
//for example, create service reference and forget to dispose or set it null
//in all this is a dangerous api, and should not expose
public Map<String, Service> getAllServices() {
return services;
}
}
// resource class is a heavy class
class Service {
}
Os fios não são recolhidos até terminarem. Eles servem como raízes da coleta de lixo. Eles são um dos poucos objetos que não serão recuperados simplesmente esquecendo-se deles ou limpando referências a eles.
Considere: o padrão básico para terminar um tópico de trabalho é definir alguma variável de condição vista pelo tópico. A thread pode verificar a variável periodicamente e usar isso como um sinal para terminar. Se a variável não for declarada volatile
, então a alteração para a variável pode não ser vista pelo fio, por isso não saberá terminar. Ou imagine se alguns tópicos querem atualizar um objeto compartilhado, mas deadlock ao tentar bloquear nele.
Se você só tem um punhado de tópicos estes bugs provavelmente serão óbvios porque o seu programa vai parar de funcionar corretamente. Se você tiver um conjunto de threads que cria mais threads conforme necessário, então os threads obsoletos/presos podem não ser notados, e irão acumular indefinidamente, causando uma fuga de memória. Os tópicos são é provável que use outros dados em sua aplicação, assim também irá impedir qualquer coisa que eles diretamente referenciar de alguma vez ser coletado.
Como exemplo de brinquedo:
static void leakMe(final Object object) {
new Thread() {
public void run() {
Object o = object;
for (;;) {
try {
sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {}
}
}
}.start();
}
Chama o que quiseres, mas o objecto que passou para leakMe
nunca morrerá.
(*editado*)
Eu acho que um exemplo válido poderia ser usar variáveis Threadlocais em um ambiente onde threads são agrupados.
Por exemplo, usando variáveis Threadlocais em Servlets para se comunicar com outros componentes da web, tendo os threads sendo criados pelo recipiente e mantendo os inactivos em um pool. Variáveis threadlocais, se não forem corretamente limpas, viverão lá até, possivelmente, o mesmo componente Web sobrepor seus valores.
Claro que, uma vez identificado, o o problema pode ser resolvido facilmente.
O entrevistador pode estar à procura de uma solução circular de referência:
public static void main(String[] args) {
while (true) {
Element first = new Element();
first.next = new Element();
first.next.next = first;
}
}
Este é um problema clássico com a contagem de referência de coletores de lixo. Você então explicaria educadamente que o JVMs usa um algoritmo muito mais sofisticado que não tem essa limitação.
- Wes Tarle.