preface:
It's updated again. The pigeon king is harmed. He hasn't spent much time reading books since he worked. Recently, he moved to a new home. The environment has changed and he is a little interested hh. The rhythm of high probability update is one article per week. It's very tired to go to work on weekdays... I hope I can stick to it ~
Item07:Eliminate expired object references
JAVA has automatic recycling function, but in some places, memory leakage will still occur if the written code is not good. Here is an example: stack implementation
public class MemoryLeakStack { private Object[] elements; private int size; private static final int DEFAULT_INITIAL_CAPACITY=16; public MemoryLeakStack(){ elements=new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e){ ensureCapacity(); elements[size++]=e; } public Object pop(){ if (size==0){ throw new EmptyStackException(); } return elements[--size]; } private void ensureCapacity() { if (elements.length==size){ //Returns a new array object, copies the old array and expands its length elements= Arrays.copyOf(elements,2*size+1); } } }
At first glance, it seems that there is no problem, but in fact, there is a security risk in the pop method. It only takes the length of the stack size –, but does not eliminate the object reference in the array, resulting in the existence of objects that have not been used all the time to survive in memory.
The improvements here are:
public Object pop(){ if (size==0){ throw new EmptyStackException(); } Object result= elements[--size]; elements[size]=null; //Eliminate references return result; }
Although it seems very simple and java also provides an automatic recycling mechanism, we should pay attention to eliminating references in daily development.
So what scenarios in JAVA need attention?
1. As long as the class manages its own memory, it needs to be vigilant. For example, the Stack example just mentioned
2. Objects are stored in the cache.
It should be noted that when an object reference is put into the cache, if it will not be used for a long time, the reference should be eliminated. Solution: you can use WeakHashMap to represent the cache.
Detailed description of WeakHashMap
and HashMap Similarly, WeakHashMap is also a hash table. Its stored content is also a key value mapping, and both keys and values can be null.
However, the key of WeakHashMap is "weak key". In the WeakHashMap, when a key is no longer used normally, it will be automatically removed from the WeakHashMap. When a key is removed, its corresponding key value pair is effectively removed from the mapping.
What about the principle of "weak bond"? Basically, it is implemented through WeakReference and ReferenceQueue.
Important contents of the WeakHashMap class:
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> { //... Entry<K,V>[] table; private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); }
It can be seen that WeakHashMap uses an empty array to store data.
There is also a ReferenceQueue for storing weak references collected by gc
Enrty in WeakHashMap:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { }
You can see that it inherits the WeakReference class. The Entry array representing the storage is a soft reference array
How to use this WeakReference and ReferenceQueue?
Code snippet of WeakHashMap:
/** * Expunges stale entries from the table. Delete old entry */ private void expungeStaleEntries() { for (Object x; (x = queue.poll()) != null; ) { synchronized (queue) { @SuppressWarnings("unchecked") //Gets the reference of the element in the ReferenceQueue queue. That is, the key value pairs that have been cleared by gc Entry<K,V> e = (Entry<K,V>) x; int i = indexFor(e.hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> p = prev; while (p != null) { Entry<K,V> next = p.next; //Find the same if (p == e) { //Linked list deletion if (prev == e) table[i] = next; else prev.next = next; // Must not null out e.next; // stale entries may be in use by a HashIterator //Set the reference of value to null e.value = null; // Help GC size--; break; } prev = p; p = next; } } } }
The specific operation is that after GC cleans up one object at a time, the reference object will be put into ReferenceQueue. expungeStaleEntries method: traverse the ReferenceQueue queue, delete the corresponding Entry in the Enrty array, and set its value to null to speed up the process of GC. Reduce the reference to value to facilitate its subsequent recycling.
- Create a new WeakHashMap and add "key value pair" to the WeakHashMap.
In fact, WeakHashMap saves entries (key value pairs) through the array table; Each Entry is actually a one-way linked list, that is, the Entry is a key value pair linked list. - When a "weak key" is no longer referenced by other objects and is recycled by GC. When the GC reclaims the "weak key", the "weak key" will also be added to the ReferenceQueue(queue) queue.
- Every time we need to operate the WeakHashMap, we will synchronize the table and queue first. All key value pairs are saved in table, while key value pairs recycled by GC are saved in queue; To synchronize them is to delete the key value pairs recycled by GC in the table.
public class ReferenceQueueTest { public static void main(String[] args) throws InterruptedException { int _1M = 1024 * 1024; ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); Thread thread = new Thread(() -> { try { int cnt = 0; WeakReference<byte[]> k; while ((k = (WeakReference) referenceQueue.remove()) != null) { System.out.println((cnt++) + "Recycled:" + k); } } catch (InterruptedException e) { // End cycle } }); //Set as daemon thread, and it will exit when the system is full of daemon threads thread.setDaemon(true); thread.start(); Object value = new Object(); Map<Object, Object> map = new HashMap<>(); for (int i = 0; i < 100; i++) { byte[] bytes = new byte[_1M]; WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes, referenceQueue); map.put(weakReference, value); } System.out.println("map.size->" + map.size()); } }