Guia do WeakHashMap em Java

Guia do WeakHashMap em Java

1. Visão geral

Neste artigo, veremosWeakHashMap do pacotejava.util.

Para entender a estrutura de dados, vamos usá-lo aqui para lançar uma implementação de cache simples. No entanto, lembre-se de que isso significa entender como o mapa funciona, e criar sua própria implementação de cache é quase sempre uma má ideia.

Simplificando, oWeakHashMap é uma implementação baseada em hashtable da interfaceMap, com chaves que são do tipoWeakReference.

Uma entrada em aWeakHashMap será automaticamente removida quando sua chave não estiver mais em uso normal, o que significa que não há um únicoReference apontando para essa chave. Quando o processo de coleta de lixo (GC) descarta uma chave, sua entrada é efetivamente removida do mapa, de modo que essa classe se comporta de maneira um pouco diferente de outras implementações deMap.

2. Referências fortes, suaves e fracas

Para entender comoWeakHashMap funciona,we need to look at a WeakReference class - que é a construção básica para chaves na implementação deWeakHashMap. Em Java, temos três tipos principais de referências, que explicaremos nas seções a seguir.

2.1. Referências Fortes

A referência forte é o tipo mais comum deReference que usamos em nossa programação do dia a dia:

Integer prime = 1;

A variávelprime tem umstrong reference para um objetoInteger com valor 1. Qualquer objeto que tenha uma referência forte apontando para ele não é elegível para o GC.

2.2. Soft References

Simplificando, um objeto que tem umSoftReference apontando para ele não será coletado como lixo até que a JVM absolutamente precise de memória.

Vamos ver como podemos criar umSoftReference em Java:

Integer prime = 1;
SoftReference soft = new SoftReference(prime);
prime = null;

O objetoprime tem uma referência forte apontando para ele.

Em seguida, estamos envolvendo a referência forteprime em uma referência suave. Depois de fazer essa referência fortenull, um objetoprime é elegível para GC, mas será coletado apenas quando a JVM absolutamente precisar de memória.

2.3. Referências Fracas

Os objetos referenciados apenas por referências fracas são coletados como lixo avidamente; o GC não vai esperar até que precise de memória nesse caso.

Podemos criar umWeakReference em Java da seguinte maneira:

Integer prime = 1;
WeakReference soft = new WeakReference(prime);
prime = null;

Quando fizemos uma referênciaprimenull, o objetoprime será coletado como lixo no próximo ciclo de GC, pois não há outra referência forte apontando para ele.

Referências de um tipoWeakReference são usadas como chaves emWeakHashMap.

3. WeakHashMap como um cache de memória eficiente

Digamos que queremos construir um cache que mantenha grandes objetos de imagem como valores e nomes de imagens como chaves. Queremos escolher uma implementação de mapa adequada para resolver esse problema.

Usar umHashMap simples não será uma boa escolha porque os objetos de valor podem ocupar muita memória. Além do mais, eles nunca serão recuperados do cache por um processo de GC, mesmo quando não estiverem mais em uso em nosso aplicativo.

Idealmente, queremos uma implementaçãoMap que permite ao GC excluir automaticamente objetos não utilizados. Quando uma chave de um objeto grande de imagem não estiver sendo usada em nosso aplicativo em nenhum lugar, essa entrada será excluída da memória.

Felizmente, oWeakHashMap tem exatamente essas características. Vamos testar nossoWeakHashMape ver como ele se comporta:

WeakHashMap map = new WeakHashMap<>();
BigImage bigImage = new BigImage("image_id");
UniqueImageName imageName = new UniqueImageName("name_of_big_image");

map.put(imageName, bigImage);
assertTrue(map.containsKey(imageName));

imageName = null;
System.gc();

await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);

Estamos criando uma instânciaWeakHashMap que armazenará nossos objetosBigImage. Estamos colocando um objetoBigImage como um valor e uma referência de objetoimageName como uma chave. OimageName será armazenado em um mapa como um tipoWeakReference.

Em seguida, definimos a referênciaimageName comonull, portanto, não há mais referências apontando para o objetobigImage. O comportamento padrão de umWeakHashMap é recuperar uma entrada que não tenha nenhuma referência a ela no próximo GC, portanto, essa entrada será excluída da memória pelo próximo processo de GC.

Estamos chamandoSystem.gc() para forçar a JVM a acionar um processo de GC. Após o ciclo de GC, nossoWeakHashMap estará vazio:

WeakHashMap map = new WeakHashMap<>();
BigImage bigImageFirst = new BigImage("foo");
UniqueImageName imageNameFirst = new UniqueImageName("name_of_big_image");

BigImage bigImageSecond = new BigImage("foo_2");
UniqueImageName imageNameSecond = new UniqueImageName("name_of_big_image_2");

map.put(imageNameFirst, bigImageFirst);
map.put(imageNameSecond, bigImageSecond);

assertTrue(map.containsKey(imageNameFirst));
assertTrue(map.containsKey(imageNameSecond));

imageNameFirst = null;
System.gc();

await().atMost(10, TimeUnit.SECONDS)
  .until(() -> map.size() == 1);
await().atMost(10, TimeUnit.SECONDS)
  .until(() -> map.containsKey(imageNameSecond));

Observe que apenas a referênciaimageNameFirst é definida comonull. A referênciaimageNameSecond permanece inalterada. Depois que o GC é acionado, o mapa conterá apenas uma entrada -imageNameSecond.

4. Conclusão

Neste artigo, examinamos os tipos de referências em Java para entender completamente comojava.util.WeakHashMap funciona. Criamos um cache simples que aproveita o comportamento deWeakHashMape testa se funciona como esperávamos.

A implementação de todos esses exemplos e trechos de código pode ser encontrada emGitHub project - que é um projeto Maven, portanto, deve ser fácil de importar e executar como está.