下面用更多的例子来说明一下各种引用的用法:
第一个场景:后退
public class LastStep { Holder d = new Holder(); SoftReference<Holder> sr = new SoftReference<Holder>(d); public static void main(String[] args) { } public Holder getHolder(){ if(sr.get() == null){ return new Holder(); }else{ return sr.get(); } } }当在比较短的时间内想再次获取已经用过的对象的时候可以这样来做, 如果还在就直接使用,不在了才创建,可以减少new 的对象的次数
第二个场景:缓存
很多时候我们可以使用一个Map作为缓存,这样的问题在于保存在map中的对象不会被垃圾回收掉。
借助于SoftReference对象的能够通过get()找回还没被回收对象的能力,可以把原本要缓存的对象改为缓存reference,这样,对象如果在内存足够的时候会被缓存,而不够的时候又会清空掉。
但是又会引入一个问题,SoftReference自己的对象又会无法清理,这里可以借助ReferenceQueue的能力,当Reference所持有的对象被GC后会把该Reference放到queue中的能力。通过遍历queue的方式来找到那个SoftReference已经不需要存在了来进行清理。
基于上诉内容,做一个对内存比较敏感的缓存,代码如下:
用于占位引起GC的Holder:
public class Holder { private static final int MB = 1024 * 1024; private byte[] holder; public Holder() { this.holder = new byte[1 * MB]; } }
class ObjectRef<T> extends SoftReference<T>{ private String key; public ObjectRef(String key, T value, ReferenceQueue<T> q) { super(value); this.setKey(key); } public String getKey() { return key; } public void setKey(String key) { this.key = key; } } public class Cache<T> { private Map<String, ObjectRef<T>> cacheData; private ReferenceQueue<T> q; public static <T> Cache<T> getInstance(){ return new Cache<T>(); } private Cache() { cacheData = new HashMap<String, ObjectRef<T>>(); q = new ReferenceQueue<T>(); } public T getData(String key){ T t = null; if(cacheData.containsKey(key)){ ObjectRef<T> sr = cacheData.get(key); t = sr.get(); } return t; } public void cache(String key, T t) { cleanCache(); ObjectRef<T> ref = new ObjectRef<T>(key, t, q); cacheData.put(key, ref); } private void cleanCache() { ObjectRef<T> ref = null; while((ref = (ObjectRef<T>) q.poll()) != null){ cacheData.remove(ref.getKey()); } } public static void main(String[] args) throws InterruptedException { Cache<Holder> c = Cache.getInstance(); { Holder d1 = new Holder(); c.cache("test", d1); Holder holder = c.getData("test"); System.out.println(holder); } Holder d2 = new Holder(); Holder d3 = new Holder(); Holder d4 = new Holder(); Holder d5 = new Holder(); Holder holder = c.getData("test"); System.out.println(holder); } }
使用JVM参数执行:
-verbose:gc -Xms2m -Xmx2m -Xmn1m -XX:SurvivorRatio=8 -XX:+PrintGCDetails
com.price.effective.create.Holder@ca0b6
null
说明其在内存足够的时候不会被回收,不够的时候才会被回收
场景三 使用WeakHashMap
对于上面的例子, 及一些用全局Map保存了长连接等大对象的场景, 合适回收这些对象的问题可以很好的使用WeakHashMap来解决。
WeakHashMap的key是一些WeakReference, 当Key失效时,其会清理该key,及该key对应的值。这样可以防止内存泄露。
场景四 检测对象被回收
虽然有finallize方法会在垃圾回收的时候执行,但是这样我们并不能很好的记录那些文件被回收了,使用虚引用,可以在不影响对象声明周期的情况下,监控对象的被回收的情况。如下:
public class ReferenceLeaning { public static void main(String[] args) { { final ReferenceQueue<Holder> q = new ReferenceQueue<Holder>(); Holder d1 = new Holder(); PhantomReference<Holder> r1 = new PhantomReference<Holder>(d1, q); new Thread(new Runnable() { @Override public void run() { while(true){ PhantomReference<Holder> ref = (PhantomReference<Holder>) q.poll(); if(ref != null){ try { Field referent = Reference.class .getDeclaredField("referent"); referent.setAccessible(true); Object result = referent.get(ref); System.out.println(result); result = null; } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }).start(); } Holder d2 = new Holder(); Holder d3 = new Holder(); Holder d4 = new Holder(); } }
因为PhantomReference的get方法永远返回null,因此用反射来得到其值, 但是例子中的这段代码又会使得对象不被回收了,因此最后又赋值为null.
其实这个例子并不好。 也没想到更有用的使用虚引用的方式,如果以后遇到了再补充。