Reference
java.lang.ref包下面,继承于Reference类,帮助gc识别对象引用如何处理
- FinalReference: 强引用,即 Object obj = new Object(); 不会被强制回收,会引起OutOfMemory异常
- WeakReference, 每次gc都会被回收,不会引起OOM
- SoftReference,只有内存不够用时,才会被gc回收,不会引起OOM
- PhantomReference,不会被回收,会引起OOM,类似强引用。作用是在回收前,放入指定的ReferenceQueue,帮助跟踪
回收后,reference.get()会返回null指针
public class Test { private static AtomicInteger finalC = new AtomicInteger(); public static class Bean { private byte[] bs = new byte[1024]; public Bean() { byte b = 1; Arrays.fill(bs, b); } @Override protected void finalize() throws Throwable { finalC.incrementAndGet(); } } private ReferenceQueue<Bean> queue = new ReferenceQueue<Bean>(); private void log(String phase) throws Exception { Field f = queue.getClass().getDeclaredField("queueLength"); f.setAccessible(true); System.out.println(phase + "~~~, queue length:" + f.get(queue) + ", final num:" + finalC); } public void test() throws Exception { log("before"); List<Reference> refs = new ArrayList<Reference>(); for(int i = 0; i < 20480; ++i) { refs.add(new WeakReference<Test.Bean>(new Bean(), queue)); // refs.add(new SoftReference<Test.Bean>(new Bean(), queue)); // refs.add(new PhantomReference<Test.Bean>(new Bean(), queue)); if(i % 3000 == 0) { int nullC = 0; for(int j = 0; j < i; ++j) { if(refs.get(j).get() == null) ++nullC; } log("number:" + i + ", null:" + nullC); } } log("2"); System.gc(); log("3"); System.runFinalization(); log("4"); } }
上述程序,跑在-Xmx=10m下,每个Bean > 1k, 循环跑 2048个,大于 20M
====== WeakReference
before~~~, queue length:0, final num:0 number:0, null:0~~~, queue length:0, final num:0 number:6000, null:4746~~~, queue length:4746, final num:4230 number:12000, null:9688~~~, queue length:9688, final num:9688 number:18000, null:17130~~~, queue length:15985, final num:15853 2~~~, queue length:17419, final num:18006 3~~~, queue length:17723, final num:18119 4~~~, queue length:19539, final num:20480
在6000个时,内存还未满,此时回收队列中已有4746个item已标识为NULL,并且4230个已经被finanlized
===== SoftReference
before~~~, queue length:0, final num:0 number:0, null:0~~~, queue length:0, final num:0 number:6000, null:0~~~, queue length:0, final num:0 number:12000, null:9197~~~, queue length:9197, final num:9197 number:18000, null:17722~~~, queue length:17722, final num:17722 2~~~, queue length:17722, final num:17722 3~~~, queue length:17722, final num:17722 4~~~, queue length:17722, final num:20480
在6000个时,内存未满,jvm不会强制回收SoftReference,直到12000已经超出内存xmx时
====== phantomReference
before~~~, queue length:0, final num:0 number:0, null:0~~~, queue length:0, final num:0 number:6000, null:6000~~~, queue length:0, final num:3294 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
不会被强制回收,于是OOM
WeakHashMap
包含一个类对象成员: private final ReferenceQueue<K> queue = new ReferenceQueue<K>(); (构建Reference可以不指定队列,但map利用被回收ref放入队列的特性,做了特殊处理)
使用WeakHashMap.Entry extends WeakReference 构建entry: tab[i] = new Entry<K,V>(k, value, queue, h, e);
Entry即是一个弱指针,弱指向key对象。即weakReference.get() = key。 每次gc的时候,weakReference指向的entry.key为被回收置为空,并且entry将被放入referenceQueue。注意,此时map中的entry对象本事不为空,value不会空,只有key为空。所以直接获取entry是要出NPE的。
为此,每次获取entry时,需要通过ReferenceQueue清理table中key为null的entry:
private void expungeStaleEntries() { Entry<K,V> e; while ( (e = (Entry<K,V>) queue.poll()) != null) { // 将选定被回收的item,从hashmap集合中置空,跳过。防止hashmap返回null entry } }
private Map<String, Bean> map; public void test() throws Exception { map = new HashMap<String, Test.Bean>(); // map = new WeakHashMap<String, Test.Bean>(); for(int i = 1; i < 10240; ++i) { map.put(String.valueOf(i), new Bean()); if(i%1000 == 0) { int size = 0; int npsize = 0; for(Entry<String, Test.Bean> entry : map.entrySet()) { ++size; if(entry.getKey() == null || entry.getValue() == null) { npsize++; } } System.out.println("num: " + i + ", map size:" + size + ", null pointer:" + npsize); } } }
=============== HashMap
num: 1000, map size:1000, null pointer:0 num: 2000, map size:2000, null pointer:0 ...... num: 8000, map size:8000, null pointer:0 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
=============== WeakHashMap
num: 1000, map size:1000, null pointer:0 num: 2000, map size:2000, null pointer:0 num: 3000, map size:872, null pointer:0 num: 4000, map size:1872, null pointer:0 num: 5000, map size:604, null pointer:0 num: 6000, map size:1604, null pointer:0 num: 7000, map size:99, null pointer:0 num: 8000, map size:1099, null pointer:0 num: 9000, map size:123, null pointer:0 num: 10000, map size:1123, null pointer:0可见,随着内存增加,entry被不停的会收,并且直接在map中被删除,不会留下空指针