又又来更新了,鸽王了害,自从工作了之后一直很少花时间看书了,最近搬了新的家环境一变,又有了点儿兴致hh,大概率更新的节奏是每周一篇吧,工作日要上班蛮累的。。。希望自己可以坚持下去吧~
JAVA中有自动的回收功能,但某些地方如果书写的代码不好仍然会出现内存泄漏。这里举个例子:栈实现
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){ //返回一个新的数组对象,复制旧数组且扩大长度 elements= Arrays.copyOf(elements,2*size+1); } } }
一眼看去,貌似没啥问题,但是其实在pop方法中存在着安全隐患,它只是仅仅将stack的长度size–,而并没有将数组中的对象引用消除,导致会存在一直无法使用的对象存活在内存中。
这里的改进方式是:
public Object pop(){ if (size==0){ throw new EmptyStackException(); } Object result= elements[--size]; elements[size]=null; //消除引用 return result; }
虽然看起来很简单且java也提供了自动回收机制,但日常开发中还是要注意消除引用。
那么JAVA中有哪些场景需要注意的呢?
需要注意的是当对象引用放入缓存中,如果它很久不会被使用,那么该引用应该进行消除。解决方法:可以使用WeakHashMap代表缓存。
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。某个键被移除时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。
WeakHashMap类的重要内容:
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> { //... Entry<K,V>[] table; private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); }
可以看出WeakHashMap是使用一个Enrty数组存储数据。
还有一个用于存储被gc回收后的弱引用的队列ReferenceQueue
WeakHashMap中的Enrty:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { }
可以看出其继承了WeakReference类。代表着存储的Entry数组是一个软引用数组
到底是如何利用这个WeakReference以及ReferenceQueue呢?
WeakHashMap代码片段:
/** * Expunges stale entries from the table. 删除旧entry */ private void expungeStaleEntries() { for (Object x; (x = queue.poll()) != null; ) { synchronized (queue) { @SuppressWarnings("unchecked") //获取ReferenceQueue队列中元素的引用。即已经被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; //找到相同 if (p == e) { //链表删除操作 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 //将value的引用设置为null e.value = null; // Help GC size--; break; } prev = p; p = next; } } } }
具体的操作就是GC每次清理掉一个对象之后,引用对象会被放到ReferenceQueue中。expungeStaleEntries方法:遍历ReferenceQueue队列,将Enrty数组中对应的Entry删除,并且将其value设置为null加速Gc的过程。减少对value的引用,方便其后续被回收。
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++) + "回收了:" + k); } } catch (InterruptedException e) { // 结束循环 } }); //设置为守护线程,,当系统中全部都是守护线程的时候就会退出 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()); } }