Não faz muito tempo, entrevistei alguns candidatos que procuravam emprego como engenheiro sênior de desenvolvimento Java. Costumo entrevistá-los e dizer: “Você pode me apresentar algumas referências fracas em Java?”. Se o entrevistador disser: “Bem, isso está relacionado à coleta de lixo?”, ficarei basicamente satisfeito, e não ficarei. Não espere que a resposta seja uma descrição de um artigo que vai ao fundo das coisas.
No entanto, contrariamente às expectativas, fiquei surpreendido ao descobrir que entre os quase 20 candidatos com uma média de 5 anos de experiência em desenvolvimento e formação altamente qualificada, apenas duas pessoas sabiam da existência de referências fracas, mas apenas uma destas duas pessoas realmente Aprenda sobre isso. Durante o processo de entrevista, também tentei sugerir algumas coisas para ver se alguém diria de repente “Então é isso”, mas o resultado foi muito decepcionante. Comecei a me perguntar por que esse conhecimento foi tão ignorado. Afinal, referências fracas são um recurso muito útil, e esse recurso foi introduzido quando o Java 1.2 foi lançado, há 7 anos.
Bem, não espero que você se torne um especialista em referências fracas depois de ler este artigo, mas acho que você deveria pelo menos entender o que são referências fracas, como usá-las e em quais cenários elas são usadas. Por se tratarem de alguns conceitos desconhecidos, explicarei brevemente as três questões anteriores.
Referência Forte
Referência forte é a referência que usamos com frequência e está escrita da seguinte forma:
Copie o código do código da seguinte forma:
Buffer StringBuffer = new StringBuffer();
O exemplo acima cria um objeto StringBuffer e armazena uma referência (forte) a esse objeto no buffer variável. Sim, esta é uma operação pediátrica (por favor, perdoe-me por dizer isso). O mais importante sobre uma referência forte é que ela pode tornar a referência forte, o que determina sua interação com o coletor de lixo. Especificamente, se um objeto for acessível através de uma sequência de links de referência forte (Fortemente alcançável), ele não será reciclado. Isso é exatamente o que você precisa se não quiser que o objeto com o qual está trabalhando seja reciclado.
Mas citações fortes são tão fortes
Em um programa, é um tanto incomum definir uma classe como não extensível. É claro que isso pode ser conseguido marcando a classe como final. Ou pode ser mais complicado, que é retornar uma interface (Interface) através de um método de fábrica que contém um número desconhecido de implementações específicas. Por exemplo, queremos usar uma classe chamada Widget, mas esta classe não pode ser herdada, portanto, novas funções não podem ser adicionadas.
Mas o que devemos fazer se quisermos rastrear informações adicionais sobre o objeto Widget? Suponha que precisemos registrar o número de série de cada objeto, mas como a classe Widget não contém esse atributo e não pode ser estendida, não podemos adicionar esse atributo. Na verdade, não há problema algum. O HashMap pode resolver completamente os problemas acima.
Copie o código do código da seguinte forma:
serialNumberMap.put(widget, widgetSerialNumber);
Aparentemente, isso pode parecer bom, mas referências fortes a objetos widget podem causar problemas. Podemos ter certeza de que quando o número de série do widget não for mais necessário, devemos remover a entrada do mapa. Se não o removermos, poderá causar vazamentos de memória ou poderemos excluir os widgets que estamos usando ao removê-los manualmente, o que pode levar à perda de dados válidos. Na verdade, esses problemas são muito semelhantes. Este é um problema que linguagens sem mecanismos de coleta de lixo costumam encontrar ao gerenciar memória. Mas não precisamos nos preocupar com esse problema, pois estamos usando a linguagem Java com um mecanismo de coleta de lixo.
Outro problema que referências fortes podem causar é o cache, especialmente para arquivos grandes como imagens. Suponha que você tenha um programa que precise processar imagens fornecidas pelos usuários. Uma abordagem comum é armazenar dados de imagem em cache, porque carregar imagens do disco é muito caro e, ao mesmo tempo, também queremos evitar ter duas cópias dos mesmos dados de imagem. na memória ao mesmo tempo.
O objetivo do cache é evitar que carreguemos arquivos desnecessários novamente. Você descobrirá rapidamente que o cache sempre conterá uma referência aos dados da imagem na memória. O uso de referências fortes forçará os dados da imagem a permanecerem na memória, o que exige que você decida quando os dados da imagem não são mais necessários e remova-os manualmente do cache para que possam ser recuperados pelo coletor de lixo. Então você é mais uma vez forçado a fazer o que o coletor de lixo faz e decidir manualmente quais objetos limpar.
Referência Fraca
Uma referência fraca é simplesmente uma referência que não é tão forte para manter o objeto na memória. Usando WeakReference, o coletor de lixo o ajudará a decidir quando o objeto referenciado será reciclado e removerá o objeto da memória. Crie uma referência fraca da seguinte forma:
Copie o código do código da seguinte forma:
eakReference<Widget> fracoWidget = new WeakReference<Widget>(widget);
Você pode obter o objeto Widget real usando fracoWidget.get(). Como referências fracas não podem impedir o coletor de lixo de reciclá-las, você descobrirá que (quando não há referência forte ao objeto widget), null é retornado repentinamente ao usar. pegar.
A maneira mais fácil de resolver o problema acima de gravação de número de sequência de widget é usar a classe WeakHashMap integrada do Java. WeakHashMap é quase igual a HashMap, a única diferença é que suas chaves (não valores!!!) são referenciadas por WeakReference. Quando uma chave WeakHashMap é marcada como lixo, a entrada correspondente a esta chave será removida automaticamente. Isso evita o problema acima de exclusão manual de objetos Widget desnecessários. WeakHashMap pode ser facilmente convertido em HashMap ou Map.
Fila de referência
Quando um objeto de referência fraca começa a retornar nulo, o objeto apontado pela referência fraca é marcado como lixo. E esse objeto de referência fraco (não o objeto para o qual ele aponta) não tem utilidade. Normalmente, alguma limpeza precisa ser feita neste momento. Por exemplo, WeakHashMap removerá entradas inúteis neste momento para evitar o armazenamento de referências fracas e sem sentido que crescem indefinidamente.
As filas de referência facilitam o rastreamento de referências indesejadas. Quando você passa um objeto ReferenceQueue ao construir WeakReference, quando o objeto apontado pela referência é marcado como lixo, o objeto de referência será automaticamente adicionado à fila de referência. Em seguida, você pode processar a fila de referência recebida em um período fixo, como fazer algum trabalho de limpeza para lidar com esses objetos de referência inúteis.
Quatro tipos de referências
Na verdade, existem quatro tipos de referências com diferentes intensidades em Java, de fortes a fracas, são referências fortes, referências suaves, referências fracas e referências virtuais. A seção acima apresenta referências fortes e referências fracas, e a seguir descreve as duas restantes, referências suaves e referências virtuais.
Referência suave
Uma referência suave é basicamente igual a uma referência fraca, exceto que tem uma capacidade mais forte de impedir que o período de coleta de lixo recicle o objeto para o qual aponta do que uma referência fraca. Se um objeto for acessível por uma referência fraca, o objeto será destruído pelo coletor de lixo no próximo ciclo de coleta. Mas se a referência suave puder ser alcançada, o objeto permanecerá na memória por mais tempo. O coletor de lixo somente recuperará objetos acessíveis por essas referências flexíveis quando não houver memória suficiente.
Como os objetos acessíveis por referências suaves permanecerão na memória por mais tempo do que os objetos acessíveis por referências fracas, podemos usar esse recurso para armazenamento em cache. Dessa forma, você pode economizar muitas coisas. O coletor de lixo se preocupará com qual tipo está atualmente acessível e com o grau de consumo de memória para processamento.
Referência Fantasma
Ao contrário das referências suaves e das referências fracas, os objetos apontados pelas referências virtuais são muito frágeis e não podemos obter os objetos para os quais eles apontam por meio do método get. Sua única função é que após o objeto para o qual aponta ser reciclado, ele é adicionado à fila de referência para registrar que o objeto apontado pela referência foi destruído.
Quando o objeto apontado por uma referência fraca torna-se acessível por uma referência fraca, a referência fraca é adicionada à fila de referência. Esta operação ocorre antes que a destruição do objeto ou a coleta de lixo realmente ocorra. Teoricamente, o objeto que está prestes a ser reciclado pode ser ressuscitado por um método destruidor não compatível. Mas esta referência fraca será destruída. Uma referência virtual é adicionada à fila de referência somente depois que o objeto para o qual ela aponta é removido da memória. Seu método get continua retornando nulo para evitar que o objeto quase destruído para o qual ele aponta seja ressuscitado.
Existem dois cenários principais de uso para referências virtuais. Permite saber exatamente quando o objeto a que se refere é removido da memória. E na verdade esta é a única maneira em Java. Isto é especialmente verdadeiro quando se lida com arquivos grandes, como imagens. Ao determinar que um objeto de dados de imagem deve ser reciclado, você pode usar referências virtuais para determinar se o objeto é reciclado antes de continuar a carregar a próxima imagem. Dessa forma, você pode evitar erros terríveis de estouro de memória, tanto quanto possível.
O segundo ponto é que as referências virtuais podem evitar muitos problemas durante a destruição. O método finalize pode ressuscitar objetos que estão prestes a ser destruídos, criando referências fortes apontando para eles. No entanto, um objeto que substitui o método finalize precisa passar por dois ciclos separados de coleta de lixo se quiser ser reciclado. No primeiro ciclo, um objeto é marcado como reciclável e pode então ser destruído. Mas porque ainda existe uma pequena possibilidade de que este objeto seja ressuscitado durante o processo de destruição. Nesse caso, o coletor de lixo precisa ser executado novamente antes que o objeto seja realmente destruído. Como a destruição pode não ser muito oportuna, um número indeterminado de ciclos de coleta de lixo precisa ser realizado antes que a destruição do objeto seja chamada. Isso significa que pode haver um grande atraso na limpeza efetiva do objeto. É por isso que você ainda recebe erros irritantes de falta de memória quando a maior parte da pilha está marcada como lixo.
Usando referências virtuais, a situação acima será resolvida. Quando uma referência virtual é adicionada à fila de referência, você não tem absolutamente nenhuma maneira de obter um objeto destruído. Porque neste momento o objeto foi destruído da memória. Como uma referência virtual não pode ser usada para regenerar o objeto para o qual aponta, seu objeto será limpo no primeiro ciclo de coleta de lixo.
Obviamente, o método finalize não é recomendado para ser substituído. Como as referências virtuais são obviamente seguras e eficientes, a remoção do método finalize pode tornar a máquina virtual significativamente mais simples. Claro, você também pode substituir esse método para conseguir mais. Tudo depende da escolha pessoal.
Resumir
Acho que vendo isso, muitas pessoas estão começando a reclamar. Por que você está falando sobre uma API antiga dos últimos dez anos? Bem, pela minha experiência, muitos programadores Java não conhecem esse conhecimento muito bem. É necessária uma compreensão profunda e espero que todos possam ganhar algo com este artigo.