Este artigo apresenta os princípios do Java Memory Management e as causas dos vazamentos de memória em detalhes.
Mecanismo de gerenciamento de memória java
No C ++, se for necessário um pedaço de memória para alocar dinamicamente uma peça de memória, o programador precisa ser responsável por todo o ciclo de vida desta peça de memória. Desde a aplicação de alocação, uso, até liberação final. Esse processo é muito flexível, mas muito pesado. A língua Java fez sua própria otimização para o gerenciamento de memória, que é o mecanismo de coleta de lixo. Quase todos os objetos de memória em Java são alocados na memória da heap (exceto nos tipos básicos de dados), e o GC (Gage Collection) é então responsável pela reciclagem automática da memória que não é mais usada.
O exposto acima é a situação básica do mecanismo de gerenciamento de memória Java. Mas se apenas entendermos isso, ainda encontraremos vazamentos de memória no desenvolvimento real do projeto. Algumas pessoas podem duvidar que, como o mecanismo de coleta de lixo de Java pode reciclar automaticamente a memória, por que ainda haverá vazamentos de memória? Nesta questão, precisamos saber quando o GC recicla objetos de memória e que tipo de objetos de memória serão considerados "não mais usados" pelo GC.
O acesso a objetos de memória no Java usa métodos de referência. No código Java, mantemos uma variável de referência de um objeto de memória. Nos programas Java, essa variável de referência em si pode ser armazenada na memória da heap e na memória da pilha de código (o mesmo que o tipo de dados básico). Os threads GC começam a rastrear das variáveis de referência na pilha de código para determinar qual memória está sendo usada. Se o thread GC não puder rastrear uma peça de memória de heap dessa maneira, o GC acredita que esse pedaço de memória não será mais usado (porque o código não pode mais acessar esta peça de memória).
Através deste método de gerenciamento de memória gráfico direcionado, quando um objeto de memória perde todas as referências, o GC pode reciclá -lo. Por outro lado, se o objeto ainda tiver uma referência, ele não será reciclado pelo GC, mesmo que a máquina virtual Java jogue o OutfMemoryError.
Vazamento de memória Java
De um modo geral, existem duas situações para vazamentos de memória. Em um caso, na linguagem C/C ++, toda a memória alocada na pilha será excluída (como a reatribuição de ponteiro) quando não for liberada; Memória e seu método de acesso (referência). O primeiro caso é que ele foi bem resolvido em Java devido à introdução do mecanismo de coleta de lixo. Portanto, os vazamentos de memória em Java se referem principalmente ao segundo caso.
Talvez apenas falar sobre o conceito seja muito abstrato, você pode dar uma olhada em esse exemplo:
A cópia do código é a seguinte:
Vetor V = novo vetor (10);
for (int i = 1; i <100; i ++) {
Objeto o = new Object ();
v.add (o);
o = nulo;
}
Neste exemplo, há referências ao objeto Vector V e referências ao objeto O objeto O na pilha de código. No loop for, geramos continuamente novos objetos e os adicionamos ao objeto vetorial e esvaziamos a referência do O. A questão é: se o GC ocorrer após a referência O está vazia, o objeto que criamos será reciclado pelo GC? A resposta é não. Porque quando o GC rastreia as referências na pilha de código, ela encontrará uma referência V e continua rastreando, ele descobrirá que há uma referência ao objeto no espaço da memória apontado pela referência V. Ou seja, embora a referência de O tenha sido vazia, ainda existem outras referências no objeto e podem ser acessadas, para que o GC não possa liberá -lo. Se o objeto não tiver efeito no programa após esse loop, pensamos que ocorreu um vazamento de memória neste programa Java.
Embora os vazamentos de memória Java sejam menos destrutivos para vazamentos de memória em C/C ++, o programa ainda pode ser executado normalmente na maioria dos casos, exceto em alguns casos em que o programa trava. No entanto, quando os dispositivos móveis têm restrições estritas à memória e CPU, o excesso de memória Java levará à ineficiência dos programas e à ocupação de grandes quantidades de memória indesejada. Isso fará com que o desempenho de toda a máquina se deteriore e, em casos graves, também fará com que o OrofMemoryError seja jogado, fazendo com que o programa trave.
Evite vazamentos de memória em geral
Em geral, sem envolver estruturas de dados complexas, os vazamentos de memória Java se manifestam à medida que o ciclo de vida de um objeto de memória excede o tempo que o programa precisa. Às vezes chamamos de "livre de objetos".
Por exemplo:
A cópia do código é a seguinte:
public class FileSearch {
Byte privado [] conteúdo;
arquivo privado mfile;
public filesearch (arquivo de arquivo) {
mfile = arquivo;
}
public boolean hasstring (string str) {
int size = getFilesize (mfile);
content = novo byte [tamanho];
loadfile (mfile, conteúdo);
String s = new string (conteúdo);
retornar S.Contains (STR);
}
}
Neste código, há uma função Hasstring na classe de pesquisa de arquivos para determinar se o documento contém a sequência especificada. O processo é carregar o mfile na memória primeiro e depois fazer julgamentos. No entanto, o problema aqui é que o conteúdo é declarado como uma variável de instância, não uma variável local. Portanto, depois que essa função retorna, os dados de todo o arquivo ainda existem na memória. É óbvio que não precisamos mais desses dados no futuro, o que leva a um desperdício irracional de memória.
Para evitar vazamentos de memória neste caso, somos obrigados a gerenciar nossa memória alocada com o pensamento de gerenciamento de memória C/C ++. Primeiro, é esclarecer o escopo efetivo do objeto de memória antes de declarar a referência do objeto. Os objetos de memória válidos em uma função devem ser declarados como variáveis locais e aqueles com o mesmo ciclo de vida que a instância da classe devem ser declarados como variáveis de instância ... e assim por diante. Segundo, lembre -se de esvaziar manualmente o objeto de memória quando não for mais necessário.
Problema de vazamento de memória em estruturas de dados complexas
Em projetos reais, geralmente usamos algumas estruturas de dados mais complexas para armazenar em cache as informações de dados necessárias durante a operação do programa. Às vezes, devido à complexidade da estrutura de dados, ou temos algumas necessidades especiais (por exemplo, o máximo possível de informações de cache para melhorar a velocidade de execução do programa etc.), é difícil para nós lidar com os dados na estrutura de dados. Neste momento, podemos usar um mecanismo especial em Java para evitar vazamentos de memória.
Introduzimos antes que o mecanismo GC do Java seja baseado no mecanismo de referência que rastreia a memória. Antes disso, as referências que usamos definiram apenas uma forma de "objeto O". De fato, essa é apenas uma situação padrão no mecanismo de referência Java, e existem outros métodos de referência. Ao usar esses mecanismos de referência especiais e combinar com o mecanismo GC, podemos atingir alguns dos efeitos de que precisamos.
Vários métodos de referência em java
Existem várias maneiras diferentes de citar em Java, a saber: forte citação, citação suave, citação fraca e citação virtual. Em seguida, primeiro entendemos o significado desses métodos de citação em detalhes.
Citação forte
As citações usadas no conteúdo que introduzimos antes eram citações fortes, que são as citações mais comuns usadas. Se um objeto tiver uma referência forte, é semelhante a uma necessidade diária essencial, e o coletor de lixo nunca o reciclará. Quando o espaço de memória é insuficiente, a máquina virtual Java prefere lançar um erro externo para fazer com que o programa termine anormalmente do que reciclar objetos com fortes referências para resolver o problema da memória.
Softreference
Um uso típico da classe Softreference é para caches sensíveis à memória. O princípio da Softreference é garantir que todas as referências suaves sejam limpas antes que a JVM relate uma memória insuficiente ao manter uma referência a um objeto. O ponto principal é que o coletor de lixo pode (ou não) liberar objetos acessíveis a suaves em tempo de execução. Se um objeto é liberado depende do algoritmo do coletor de lixo e da quantidade de memória disponível quando o coletor de lixo está em execução.
Referência fraca
Um uso típico da classe de referência fraca é normalizar o mapeamento (mapeamento canônico). Além disso, referências fracas também são úteis para objetos com vitalícios relativamente longos e baixa sobrecarga de recreação. O ponto principal é que, se um objeto fracamente acessível for encontrado durante a execução do coletor de lixo, o objeto referenciado pela fracos referença será liberado. No entanto, observe que o coletor de lixo pode ter que ser executado várias vezes antes de encontrar e liberar objetos fracamente acessíveis.
Phantomreference
A classe Phantomreference só pode ser usada para rastrear as próximas coleções de objetos referenciados. Da mesma forma, ele também pode ser usado para executar operações de limpeza pré-mortem. O fantasma deve ser usado com a classe ReferenceQueue. O ReferenceQueue é necessário porque pode atuar como um mecanismo de notificação. Quando o coletor de lixo determina que um objeto é um objeto de acesso virtual, o objeto fantasma é colocado em sua referência. Colocar o objeto Phantomreference no ReferenceQueue é uma notificação indicando que o objeto referenciado pelo objeto Phantomreference terminou e está disponível para coleta. Isso permite que você tome medidas antes de a memória ocupada pelo objeto ser reciclada. Referência e referência são usados em conjunto com a referência.
GC, interação de referência e referência
R. O GC não pode excluir a memória de objetos com fortes referências.
B. GC encontrou uma memória de objeto apenas com referências suaves, então:
① O campo de referência do objeto Softreference é definido como NULL, para que o objeto não se renda mais ao objeto Heap.
Object O objeto de heap referenciado pela Softreference é declarado como finalizável.
③ Quando o método Finalize () do objeto Heap é executado e a memória ocupada pelo objeto é liberada, o objeto Softreference é adicionado ao seu referencequeue (se o último existir).
C. GC descobre uma memória de objeto com apenas referências fracas, então:
① O campo de referência do objeto de referência fraca é definido como NULL, para que o objeto não se renda mais ao objeto Heap.
Object O objeto heap referenciado pela fraco referência é declarado como finalizável.
③ Quando o método Finalize () do objeto Heap é executado e a memória ocupada pelo objeto é liberada, o objeto de referência fraca é adicionado ao seu referencequeue (se o último existir).
D. GC descobre uma memória de objeto que apenas possui referências virtuais, então:
Object O objeto Heap referenciado pela fantasma é declarado como finalizável.
② O fantasma é adicionado ao seu referencequeue antes que o objeto Heap seja liberado.
Vale a pena notar os seguintes pontos:
1. O GC não encontrará objetos de memória de referência suave em geral.
2. A descoberta e a liberação de referências fracas do GC não são imediatamente.
3. Quando referências suaves e referências fracas são adicionadas ao ReferenceQueue, as referências à memória real foram definidas para vazias e a memória relevante foi liberada. Ao adicionar referência virtual ao ReferenceQueue, a memória ainda não foi lançada e ainda pode ser acessada.
Através da introdução acima, acredito que você tem um certo entendimento do mecanismo de citação Java e das semelhanças e diferenças de vários métodos de citação. Apenas um conceito pode ser muito abstrato.
A cópia do código é a seguinte:
String str = new String ("Hello");
ReferenceQueue <string> rq = new ReferenceQueue <string> ();
FrawReference <string> wf = new frActReference <string> (str, rq);
str = nulo;
String str1 = wf.get ();
// Se o objeto "Hello" não for reciclado, rq.poll () retorna nulo
Referência <?
No código acima, preste atenção aos dois lugares ⑤⑥. Se o objeto "Hello" não for reciclado wf.get () retornará o objeto "Hello", rq.poll () retornará nulo; null, rq.poll () retorna um objeto de referência, mas não há referência ao objeto STR neste objeto de referência (o Phantomreference é diferente da referência fraca e do softripence).
Aplicação conjunta do mecanismo de citação e estruturas de dados complexas
Ao entender o mecanismo GC, o mecanismo de referência e a combinação com a Referênciaqueue, podemos implementar alguns tipos de dados complexos que impedem o excesso de memória.
Por exemplo, a Softreference possui as características de criar um sistema de cache, para que possamos implementar um sistema de cache simples em combinação com tabelas de hash. Isso não apenas garante que o máximo de informações possa ser armazenado em cache, mas também garante que a máquina virtual Java não jogue fora do MemoryError devido ao vazamento de memória. Esse mecanismo de cache é particularmente adequado para situações em que os objetos de memória têm um ciclo de vida longo e o tempo para gerar objetos de memória é relativamente longo, como imagens de capa da lista de cache etc. Para alguns casos em que o ciclo de vida é longo, mas a sobrecarga de geração de objetos de memória não é grande, o uso da referência fraca pode alcançar um melhor gerenciamento de memória.
Uma cópia do código -fonte do SofThashmap está anexada.
A cópia do código é a seguinte:
pacote com.
//: softashmap.java
importar java.util.
importar java.lang.ref.
importar android.util.log;
classe pública SofThashmap estende abstrataMap {
/** O hashmap interno que segurará a softreference.
mapa final privado hash = new hashmap ();
/** O número de referências "duras" para manter internamente.
private final int hard_size;
/** A lista FIFO de referências duras, ordem do último acesso.
Final LinkedList privado hardcache = new LinkedList ();
/** Fila de referência para objetos de Softreference limpos.
Fila de referência privada = new ReferenceQueue ();
// Número de referência forte
public SofThashmap () {this (100);
public SofThashmap (int HardSize) {hard_size = hardsize;
Public Object Get (chave do objeto) {
Resultado do objeto = nulo;
// Temos a softreference representada por essa chave
Softriference soft_ref = (softreference) hash.get (chave);
if (soft_ref! = null) {
// a partir da Softreference, obtemos o valor, o que pode ser
// nulo se não estivesse no mapa, ou foi removido em
// o método processqueue () definido abaixo
resultado = soft_ref.get ();
if (resultado == null) {
// Se o valor tiver sido coletado de lixo, remova o
// entrada do hashmap.
hash.remove (chave);
} outro {
// agora adicionamos este objeto ao início do difícil
// fila de referência.
// uma vez, porque as procedências da fila do FIFO são lentas, então
// Não queremos pesquisá -lo sempre para remover
// duplicados.
// Mantenha o objeto de uso recente na memória
hardcache.addfirst (resultado);
if (hardcache.size ()> hard_size) {
// Remova a última entrada se estiver mais longa que Hard_size
hardcache.removelast ();
}
}
}
resultado de retorno;
}
/** Definimos nossa própria subclasse de Softreference que contém
Não apenas o valor, mas também a chave para facilitar a localização
A entrada no hashmap após o lixo foi coletada.
Classe estática privada SoftValue estende Softreference {
Chave de objeto final privado;
/** Você sabia que uma classe externa pode acessar dados privados
Membros e métodos de uma classe interna?
Eu pensei que era apenas a classe interna que poderia acessar o
Informações privadas da classe externa.
Acesse membros particulares de uma classe interna dentro de seu interior
aula. */
SoftValue privado (objeto K, chave do objeto, referênciaqueue q) {
super (k, q);
este .Key = key;
}
}
/** aqui passamos pelo referencequeue e removemos o lixo
coletou objetos softvalue do hashmap, olhando -os
Usando o membro de dados do softvalue.key.
public void processqueue () {
Softvalue sv;
while ((SV = (SoftValue) Queue.poll ())! = null) {
if (sv.get () == null) {
Log.e ("Processqueue", "NULL");
} outro {
Log.e ("processqueue", "não nulo");
}
hash.remove (sv.key);
Log.e ("SofThashmap", "Release" + sv.key);
}
}
/** aqui colocamos a chave, par de valores no hashmap usando
um objeto SoftValue.
public Object Put (chave do objeto, valor do objeto) {
Processqueue ();
Log.e ("SofThashmap", "colocado em" + key);
retornar hash.put (chave, novo softvalue (valor, chave, fila));
}
Public Object Remover (chave do objeto) {
Processqueue ();
retornar hash.remove (chave);
}
public void clear () {
hardcache.clear ();
processqueue ();
hash.clear ();
}
public int size () {
Processqueue ();
retornar hash.size ();
}
Public Set EntrySet () {
// Não, não, você pode não fazer isso !!! grrr
lançar novo UnsupportEdOperException ();
}
}