Usage of WeakHashMap? [duplicate] - java

This question already has answers here:
When would you use a WeakHashMap or a WeakReference?
(10 answers)
Closed 4 years ago.
WeakHashMap is an implementation of Map interface where the memory of the value object can be reclaimed by Grabage Collector
if the corresponding key is no longer referred by any section of program. So if key is no longer used in program. its Entry
object will be garbage collected irrespective of its usage. Its clear till here
This is different from HashMap where the value object remain in HashMap even if key is no longer referred. We need to explicitly call
remove() method on HashMap object to remove the value. calling remove will just remove the entry from map. Its readyness for GC will
depend whether it is still used somewhere in program or not.
Please find this coding example explaining above
Usage of WeakHashMap over HashMap as per mine understanding
My understanding is we should go for WeakHashMap only when we want to ensure that value object is reclaimed by Grabage Collector when
key is no longer referred by any section of program. This makes program memory efficient Is my understanding correct here?
Usage of WeakHashMap as per JavaDocs , i could spot this statement
This class is intended primarily for use with key objects whose equals
methods test for object identity using the == operator.
I did not get what above statement meant and how it contrast with mine understanding of WeakHashMap usage. Actually i did not get how this statement is related to usage of WeakHashMap?
UPDATE:- on further carefully reading below statement the javadocs
An entry in a WeakHashMap will automatically be removed when its key
is no longer in ordinary use. More precisely, the presence of a
mapping for a given key will not prevent the key from being discarded
by the garbage collector, that is, made finalizable, finalized, and
then reclaimed. When a key has been discarded its entry is effectively
removed from the map, so this class behaves somewhat differently from
other Map implementations.
i am revising my understanding for the benefit of me and others
Usage of WeakHashMap over HashMap as per mine revised understanding
We should go for WeakHashMap only when we want to ensure that key-value pair is removed from map on GC run when key is no longer in ordinary use other than map itself.
Examples are :-
WeakHashMap<Integer, String> numbers = new WeakHashMap<Integer, String>();
numbers.put(new Integer(1), "one");// key only used within map not anywhere else
numbers.put(new Integer(2), "two");
System.out.println(numbers.get(new Integer(1))); // prints "one"
System.gc();
// let's say a garbage collection happens here
System.out.println(numbers.get(new Integer(1))); // prints "null"
System.out.println(numbers.get(new Integer(2))); // prints "null"
Object key = new Object();
m1.put(key, c1);
System.out.println(m1.size());
key = null or new Object() ; // privious key only used within map not anywhere else
System.gc();
Thread.sleep(100);
System.out.println(m1.size());

This is due to the fact that objects will be garbage collected (GCed) when they are no longer have a strong reference from any other part of the program.
Given a WeakHashMap<MyObject, String> then if we do the following:
MyObject mo = new MyObject();
map.put(mo, "Test");
mo = null;
Then the entry mo -> Test will be eligible for GC. This means that if you have a custom .equals implementation that uses some property of MyObject to test for equality then you cannot later do this:
MyObject mo2 = new MyObject();
map.get(mo2);
Because even though your overridden .equals method may say that mo2.equals(mo) == true it is not the case that mo2 == mo and therefore the entry may have already been GCed.
The point is that if you keep a reference to mo and use that to retrieve the value from the Map then it is the case that that reference must == mo and therefore two things are true:
the entry mo -> Test cannot be gced
you can use an == based .equals method to retrieve the entry from the map
Basically; as the GC will use strong references to test whether an object can be GCed it is best to ensure that your .equals method does the same to avoid confusion.

The documentation means that this code is not very useful:
WeakHashMap<Integer, String> numbers = new WeakHashMap<Integer, String>();
numbers.put(new Integer(1), "one");
numbers.put(new Integer(2), "two");
System.out.println(numbers.get(new Integer(1))); // prints "one"
// let's say a garbage collection happens here
System.out.println(numbers.get(new Integer(1))); // prints "null"
System.out.println(numbers.get(new Integer(2))); // prints "null"
This would happen for any class where different instances can be equal. The javadoc is just warning you, in case you hadn't noticed already, that this is not helpful.

Run this test
Object key = new Object();
WeakHashMap m = new WeakHashMap();
m.put(key, 1);
System.out.println(m.size());
key = null;
System.gc();
Thread.sleep(100);
System.out.println(m.size());
though System.gc does not guarantee running GC but on my Oracle's JVM 7 it always runs and this test prints
1
0
which means that GC removed the entry from map because the key is not referenced from anywhere but map itself

I think that this means that the object test for equality be using the objects location in memory and not just because two different instances happen to 'equal' in their values.
If this is not the case the it is difficult to tell if the key is referenced any more and a different, but 'equal'in values, instance might be referenced and used to look up the value.
It might be clearer if it said that the keys have to have reference equality so that you know when the keys are no longer referenced by anything other that the WeakHashMap instance

Building on Evgeniy Dorofeev answer, here is an example with a few gotchas:
import java.util.Map;
import java.util.WeakHashMap;
public class WeakHashMapTest {
public static void main(String[] args) throws InterruptedException {
String key = new String();
Map<String, String> m = new WeakHashMap();
m.put(key, "value");
System.out.println(m.size());
m.put("key", "value");
System.out.println(m.size());
m.put(new String(), "value");
System.out.println(m.size());
m.put(new String("k"), "value");
System.out.println(m.size());
key = null;
System.gc();
Thread.sleep(100);
System.out.println(m.size());
}
}
Output
1
2
2
3
1

Related

Will a HashMap<String, WeakReference<>> potentially cause a memory leak if not explicitly cleaned up?

I've run across a piece of code that I am convinced will cause an inadvertent memory leak:
Object user = getUser("Bob");
Map<String, WeakReference<Object>> map = new HashMap<>();
map.put("Bob", new WeakReference( user ) );
The purpose of this map is to cache the Objects and to have them automatically cleared from the map by the GC when they are no longer strongly referenced.
However, the way I see it is if the key isn't a weak reference as well, then once the Object is GC'ed, there will still be an entry in the hash map with the key pointing to null. Hence the map will still contain the same number of rows, just all pointing to null values.
So in the above example, once all strong references to user are released, and the GC destroys the Object, the entry in the map will be equiv to :
map.put("Bob", null );
So unless there is a cleanup routine that flushes all keys with null values, my map will continue to grow.
So then the question becomes how to fix this? Is there a map construct that I can use which will automatically flush my entry if the value is destroyed?
I contemplated doing something like:
Object user = getUser("Bob");
Map<String, WeakReference<Object>> map = new WeakHashMap<>();
map.put(user.getUsername(), new WeakReference( user ) );
But that seems like a very limited use case where my key has to be an object retrieved from my value. With the WeakHashMap my key cannot be a String constant (ie: "Bob") or there won't be any other references to it, and the GC will clear the object from my map.
Is there some other cache construct that provides all this functionality instead?
You are right in that the collection of the referent does not remove the mapping, however, the result is not equivalent to
map.put("Bob", null );
it will be equivalent to
map.put("Bob", new WeakReference<>(null) );
so you’re not only having a dangling entry instance, but also a dangling cleared WeakReference instance.
When you use
Map<String, WeakReference<User>> map = new WeakHashMap<>();
User user = getUser("Bob");
map.put(user.getUsername(), new WeakReference( user ) );
you get the desired semantics, assuming that user.getUsername() returns a reference to the string instance stored in the User object, to ensure that it stays strongly reachable as long as the User is strongly reachable.
I don’t see any limitation here. Since the string instance stored within the User does exist, there is no overhead in referencing exactly the same string instance as map key as long as the User instance exist. You can still use string constants as lookup key ala User u = map.get("Bob");, as their equality still is determined in terms of String.hashCode() and String.equals(). If you put the mapping using the string constant "Bob" the mapping will usually persist at least as long as the code containing the constant is alive (and each other code that used the same literal during this lifetime), likely the entire application. But where’s the sense in using a different string instance as key than stored in the referent?
Note that WeakHashMap has to deal with the same issue, entries are not removed automatically. It has to use a ReferenceQueue to discover when a referent has been collected to remove its associated entry from the table. This cleanup happens whenever you invoke a method on it, so when you don’t invoke methods on it, the table won’t get cleanup, but since this cleanup happens for every insertion, you are protected against the ever-growing scenario.
if the key isn't a weak reference as well, then once the Object is GC'ed, there will still be an entry in the hash map with the key pointing to null.
With the key pointing to a non-null WeakReference which points to a null referent.
Hence the map will still contain the same number of rows, just all pointing to null values.
No, see above.
The solution is to use a WeakHashMap<String, WeakReference<Object>>, which has a background activity that spots the weak keys being collected, via a ReferenceQueue, and removes the corresponding mappings. Better still, a WeakHashMap<String, WeakReference<User>>. However:
If the keys are string literals they aren't collectable anyway.
It's a bit rich to describe this as a memory leak. The Map itself is a memory leak. If you don't want references to your objects, don't store them in a Map.
Your reasoning is correct.
Map<String,WeakReference<Object>> is used in situations when "lingering" keys do not present a problem, because they are a lot smaller than the objects stored in the map. In situations like that having lots of keys mapped to emptied weak references does not strain the memory resources of the system enough for you to notice.
You can use WeakHashMap<K,V> for situations when you would rather have keys garbage collected, but you are absolutely right about using String constants with it: this would indeed defeat the purpose. Typically, you use a custom key type that overrides equals with reference equality.

Which key instance is retained by a Map if the entry already exists?

Consider the following map:
Map<Foo,Bar> fooBarMap = new Map<Foo,Bar>();
And the following two instances of Foo, foo1 and foo2:
foo1 != foo2
foo1.equals(foo2)
(i.e. they are separate but equivalent). Now, consider the following insertions:
fooBarMap.put(foo1,bar1);
fooBarMap.put(foo2,bar2);
Question: which key is retained? Does foo2 replace foo1?
Put another way, does putting the same key twice update the key and value, or just the value?
(If you're wondering why I'm asking, it's because I'm using a WeakHashMap and must make sure I don't release references prematurely).
Thanks in advance...
This sample code:
final Map<String, String> map = new HashMap<>();
final String foo1 = new String("foo");
final String foo2 = new String("foo");
map.put(foo1, "bar");
map.put(foo2, "bar");
System.out.println(map.keySet().iterator().next() == foo1);
System.out.println(map.keySet().iterator().next() == foo2);
prints:
true
false
It seems pretty logical, since the .keySet() of a Map is a Set, and a Set will not replace a value which is already has. The javadoc for Set's .add() says that it:
Adds the specified element to this set if it is not already present (optional operation).
Note that the "optional operation" is because the doc also says that UnsupportedOperationException is thrown if the Set implementation does not support addition.
The official Sun/Oracle implementation of HashMap does the following.
With this statement sequence:
map.put(foo1, "bar1");
map.put(foo2, "bar2");
If foo2 and foo1 have the same hashCode(), and either (foo2 == foo1) or (foo2.equals(foo1)) then:
the existing key (foo1) will be retained, but
its mapped value will be changed to "bar2"
Aside: (foo2 == foo1) does not imply (foo2.equals(foo1)), because not all equals() implementations perform the '==' test -- even though they should. Also, (foo2.equals(foo1)) does not imply (foo1.equals(foo2))!
It will depend on the Map implementation. Most implementations that I've seen will keep the first key, because the logic tends to be "find the entry by the key, and if it exists, update the value in the entry", i.e. it doesn't over-write the key ref.

Understanding Java References in Java collections

I am having trouble understanding some simple code relating with Lists and maps. Take the code below as an example:
public class test {
private Map<Integer, List<String>> myMap ;
public test(){
myMap = new HashMap<Integer, List<String>>();
}
public void addToMap(String ss){
List<String> myTemp = myMap.get(ss);
Random r = new Random();
if(myTemp == null){
myTemp = new ArrayList<String>();
myMap.put(r.nextInt(100), myTemp);
}
myTemp.add(ss);
}
public Map<Integer, List<String>> getMap1(){
return myMap;
}
public static void main(String args[]){
test myTest = new test();
myTest.addToMap("abdc");
myTest.addToMap("eeer");
System.out.println(myTest.getMap1());
}
}
How exactly does the addToMap() add a new element to the mylist Map. More specifically, how does myTemp.add(ss) add a new element to myMap when myTemp is a local variable and gets deleted once its done executing. Moreover, removing myTemp.add(ss) from the addToMap() method prints out an empty HashMap in the main method, why is this? How does the put method insert the element into the map when it is executed before the add method? Thanks.
Edit: I edited the code to make a little more sense.
Unlike C++, in Java all variables are references to the real objects. So, when you do
myTemp = new Object();
you create an object in the heap whose reference is copied to the stack variable myTemp. If the reference is added to the list (or other variable), a copy of the reference is added there.
Once myTemp is destroyed, the object continues alive. The exception is when all the references to the object are gone; then the object can not be reached by the code and Garbage Collection can (but it is not forced to) delete it from memory.
myTemp isn't the thing - the list - it is instead a reference to the thing. But it isn't that specific reference which you are putting into the map; it is the thing to which it refers. The map has its own reference to the thing, so deleting the myTemp reference doesn't affect the map.
myTemp is a reference to the list. The list is allocated in heap memory and myTemp is actually is a pointer stored on the stack. Whenever you exit the function it gets deleted but the memory allocated in heap memory won't get deleted. Memories allocated in heap memory release when there is no reference to them by Garbage Collector.
You are right. When you leave the addToMap() method, the myTemp will be released(Actually it will be poped from the stack) but the list which is allocated in heap memory space will remain. Everything which is created using new keyword, will be placed inside heap memory space.
Read this link Java Reference Objects
From a best practice point of view, calling a HashMap "mylist" is problematic in a number of ways. It isn't orthogonal and "mylist" doesn't tell you a lot about its usage.
In answer to your question, it's a simple case of, if the item exists in the map, it is retrieved and the string added to it, if not the list is created and added to the map. As long as the map is in scope, the references to any lists added to it (even if they were created in the scope of a function) remain in scope themselves.
However, this isn't a helpful example at all as it mixes its naming and provides no real world use-case.

WeakHashMap - what is its purpose and how should it be used correctly

Today I found this blog post which discussed usages of WeakHashMap over cache. It was intrigued by the fact that not the values, but the keys are stored as weak references, and when the reference is no more alive, the entire key-value pair is removed from the WeakHashMap. This would therefore cause the following to happen:
WeakHashMap map = new WeakHashMap();
SomeClass myReference1 = ....
map.put(new Long(10), myReference1);
// do some stuff, but keep the myReference1 variable around!
SomeClass myReference2 = map.get(new Long(10)); // query the cache
if (myReference2 == null) {
// this is likely to happen because the reference to the first new Long(10) object
// might have been garbage-collected at this point
}
I am curious what scenarios then would take advantage of the WeakHashMap class?
When you want to attach metadata to an object for which you don't control the lifecycle. A common example is ClassLoader, though care must be taken to avoid creating a value->key reference cycle.
There are many uses, but one really important one is when you want to key something by Class. Maintaining a strong reference to Class instances can peg entire classloaders.
As an aside, Guava has a much more complete set of non-strong reference mapping constructs.
I ran the sample code to understand the difference between HashMap and WeakHashMap, Hope it helps
Map hashMap= new HashMap();
Map weakHashMap = new WeakHashMap();
String keyHashMap = new String("keyHashMap");
String keyWeakHashMap = new String("keyWeakHashMap");
hashMap.put(keyHashMap, "helloHash");
weakHashMap.put(keyWeakHashMap, "helloWeakHash");
System.out.println("Before: hash map value:"+hashMap.get("keyHashMap")+" and weak hash map value:"+weakHashMap.get("keyWeakHashMap"));
keyHashMap = null;
keyWeakHashMap = null;
System.gc();
System.out.println("After: hash map value:"+hashMap.get("keyHashMap")+" and weak hash map value:"+weakHashMap.get("keyWeakHashMap"));
The output will be:
Before: hash map value:helloHash and weak hash map value:helloWeakHash
After: hash map value:helloHash and weak hash map value:null

HashMap key problems

I'm profiling some old java code and it appears that my caching of values using a static HashMap and a access method does not work.
Caching code (a bit abstracted):
static HashMap<Key, Value> cache = new HashMap<Key, Value>();
public static Value getValue(Key key){
System.out.println("cache size="+ cache.size());
if (cache.containsKey(key)) {
System.out.println("cache hit");
return cache.get(key);
} else {
System.out.println("no cache hit");
Value value = calcValue();
cache.put(key, value);
return value;
}
}
Profiling code:
for (int i = 0; i < 100; i++)
{
getValue(new Key());
}
Result output:
cache size=0
no cache hit
(..)
cache size=99
no cache hit
It looked like a standard error in Key's hashing code or equals code.
However:
new Key().hashcode == new Key().hashcode // TRUE
new Key().equals(new Key()) // TRUE
What's especially weird is that cache.put(key, value) just adds another value to the hashmap, instead of replacing the current one.
So, I don't really get what's going on here. Am I doing something wrong?
edit
Ok, I see that in the real code the Key gets used in other methods and changes, which therefore get's reflected in the hashCode of the object in the HashMap. Could that be the cause of this behaviour, that it goes missing?
On a proper #Override of equals/hashCode
I'm not convinced that you #Override (you are using the annotation, right?) hashCode/equals properly. If you didn't use #Override, you may have defined int hashcode(), or boolean equals(Key), neither of which would do what is required.
On key mutation
If you are mutating the keys of the map, then yes, trouble will ensue. From the documentation:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
Here's an example:
Map<List<Integer>,String> map =
new HashMap<List<Integer>,String>();
List<Integer> theOneKey = new ArrayList<Integer>();
map.put(theOneKey, "theOneValue");
System.out.println(map.containsKey(theOneKey)); // prints "true"
theOneKey.add(42);
System.out.println(map.containsKey(theOneKey)); // prints "false"
By the way, prefer interfaces to implementation classes in type declarations. Here's a quote from Effective Java 2nd Edition: Item 52: Refer objects by their interfaces
[...] you should favor the use of interfaces rather than classes to refer to objects. If appropriate interface types exist, then parameters, return values, variables, and fields should all be declared using interface types.
In this case, if at all possible, you should declare cache as simply a Map instead of a HashMap.
I'd recommend double and triple checking the equals and hashCode methods. Note that it's hashCode, not hashcode.
Looking at the (abstracted) code, everything seems to be in order. It may be that the actual code is not like your redacted version, and that this is more a reflection of how you expect the code to work and not what is happening in practice!
If you can post the code, please do that. In the meantime, here are some pointers to try:
After adding a Key, use exactly the same Key instance again, and verify that it produces a cache hit.
In your test, verify the hashcodes are equal, and that the objects are equal.
Is the Map implementation really a HashMap? WeakHashMap will behave in the way you describe once the keys are no longer reachable.
I'm not sure what your Key class is, but (abstractly similarly to you) what I'd do for a simple check is:
Key k1 = new Key();
Key k2 = new Key();
System.out.println("k1 hash:" + k1.hashcode);
System.out.println("k2 hash:" + k2.hashcode);
System.out.println("ks equal:" + k1.equals(k2));
getValue(k1);
getValue(k2);
if this code shows the anomaly -- same hashcode, equal keys, yet no cache yet -- then there's cause to worry (or, better, debug your Key class;-). The way you're testing, with new Keys all the time, might produce keys that don't necessarily behave the same way.

Categories