title: 对象回收与引用类型
date: 2017-03-22 22:16:45
tags:
- Java
categories: Java
引用分类
- 强引用:日常使用的引用
- 软引用:当内存空间不足时,不论是否引用都进行回收的引用
- 弱引用:只要发生GC,就被回收的引用
- 虚引用:“引用不到”的引用类型
四个引用强度自上而下逐级递减,一个对象存在多个引用时以最强的引用类型为准;
除了以上四个引用类型其实还有两个:FinalReference,Finalizer
使用
SoftReference
非常适合拿来做缓存,实现类似下面:
ReferenceQueue<Map<String, Object>> queue = new ReferenceQueue<>();
Map<String, Object> map = new HashMap<>();
SoftReference<Map<String, Object>> softReference = new SoftReference<>(map, queue);
Map<String, Object> cache = softReference.get();
// 使用cache
if (cache == null) {
cache = new HashMap();
}
// 清理
Reference reference;
while ((reference = queue.poll()) != null) {
clearn();
}
WeakReference
通常只要发生GC,就会回收WeakReference引用的对象。
当引用时常量是无法回收,还是可以通过弱引用获取,例如:
@Test
public void weakReference() {
String string = new String("zhangh.tk");
WeakReference<String> reference = new WeakReference<>(string);
assert reference.get() != null;
string = null;
runFinalization();
assert reference.get() == null;
}
@Test
public void weakReferenceWithFinal() {
String finalStr = "zhangh.tk";
WeakReference<String> reference = new WeakReference<>(finalStr);
assert reference.get() != null;
finalStr = null;
runFinalization();
assert reference.get() != null;
}
典型的使用场景:
Thread实例中维护的ThreadLocalMap中key使用的是WeakReference<ThreadLocal<?>>
。
当不存在对ThreadLocal的强引用时,只有ThreadLocalMap对它存在弱引用,GC就可以回收ThreadLocal对象。否则只要Thread对象存在,那么始终保持对ThreadLocal的引用,就不能回收ThreadLocal,造成内存泄露。
与上面ThreadLocalMap用法类似,JDK提供了WeakHashMap。
无论WeakHashMap还是ThreadLocalMap都存在当key被回收,value仍然存在的问题。
两者解决此问题的思路也很类似,都是在类似size,add等方法时清理key为null的value避免内存泄露。
@Test
public void weakHashMapOOM() {
List<WeakHashMap<byte[][], byte[][]>> list = new ArrayList<>();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
WeakHashMap<byte[][], byte[][]> map = new WeakHashMap<>();
map.put(new byte[10000][1000], new byte[10000][1000]);
list.add(map);
runFinalization();
}
}
@Test
public void weakHashMapNotOOM() {
List<WeakHashMap<byte[][], byte[][]>> list = new ArrayList<>();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
WeakHashMap<byte[][], byte[][]> map = new WeakHashMap<>();
map.put(new byte[10000][1000], new byte[10000][1000]);
list.add(map);
runFinalization();
map.size();
}
}
PhantomReference
说它是引用不到的引用类型是因为使用PhantomReference无法获取到指向的对象,如果引用有强度那他的引用强度实在是太弱了。
使用PhantomReference必须配合ReferenceQueue,关于ReferenceQueue后面再说。
FinalReference&Finalizer
这两个类是父子关系,并且都是不公开的。若一个对象没有实现finalize方法,可以直接被回收,若实现了finalize方法由Finalizer处理。
对象回收执行大致过程:
- 当对象不可达时,若未覆盖finalize方法直接回收,否则加入F-Queue队列
- FinalizerThread线程从F-Queue里面,不停的获取数据,然后调用相应的finalize方法
- finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”
关于对象复活:finalize方法只能被JVM调用一次,也就是最多两条命,不存在不停复活的情况。
对象从创建到被回收状态转换:
对象复活示例:
class Reclaimable {
static Reclaimable staticVar;
@Override
protected void finalize() throws Throwable {
staticVar = this;
}
}
@Test
public void reclaimed() {
Reclaimable reclaimable = new Reclaimable();
reclaimable = null;
runFinalization();
assert Reclaimable.staticVar != null;
Reclaimable.staticVar = null;
runFinalization();
assert Reclaimable.staticVar == null;
}
引用队列
引用队列是垃圾收集器向应用程序返回关于对象生命周期的信息的一种方式。当一个引用对象被回收后可以选择加入引用队列,做最后的清理工作。
例如上文提到的WeakHashMap就是用引用队列收集失效引用,根据引用队列数据清除Entry和值对象。
再例如,因为不能从PhantomReference获得引用的对象,但是可以从引用队列中获得对象被回收的通知。
可以使用引用队列和PhantomReference观察对象的复活:
@Test
public void phantomQueueWithUnReclaimed() {
class A{}
ReferenceQueue<A> queue = new ReferenceQueue<>();
PhantomReference<A> reference = new PhantomReference<>(new A(), queue);
assert reference.get() == null;
assert queue.poll() == null;
runFinalization();
assert reference.get() == null;
assert queue.poll() != null;
}
@Test
public void phantomQueueWithReclaimed() {
ReferenceQueue<Reclaimable> queue = new ReferenceQueue<>();
PhantomReference<Reclaimable> reference = new PhantomReference<>(new Reclaimable(), queue);
assert reference.get() == null;
assert queue.poll() == null;
runFinalization();
assert reference.get() == null;
assert queue.poll() == null;
Reclaimable.staticVar = null;
runFinalization();
assert queue.poll() != null;
}