一、概念
- 强引用就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还"活着",垃圾收集器不会处理这种对象。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应引用赋值为null,就是可以被垃圾收集的了,当然具体回收时机还需要看垃圾收集策略。我们使用的大部分引用都是强引用,这是使用最普遍的引用。如:
String strongReference = new String("refenrence");
有一点需要注意的是像下面这种代码中list集合里的数据并不会进行释放,即使内存不足时也不会进行释放:
String strongReference = new String("refenrence");
List<String> list = new ArrayList<>();
list.add(strongReference);
list = null;
System.out.println(list);
System.out.println(strongReference);
这是因为ArrayList是使用数组来实现的,在ArrayList中定义了一个elementData[],如果没有指定ArrayList大小这个数组默认是为空的,只有在第一次添加元素时才会默认初始化容量为10,ArrayList类有一个java.util.ArrayList#clear方法用来清空集合中的元素,会将集合中的每个元素执行
elementData[i] = null;
不同于
list = null;
强引用仍然存在,避免在后续调用add()方法添加元素时进行重新的内存分配。使用如clear()方法释放内存的方法对数组中存放的引用类型非常适用,这样就可以及时释放内存。
- 软引用是一种相对弱化一些的引用,可以让对象豁免一些垃圾收集,只有当JVM认为内存不足时才会去试图回收软引用指向的对象。JVM会确保在抛出OutOfMemoryError之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时就清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
String str = "软引用";
SoftReference softReference = new SoftReference(str);
str = null;
System.out.println(softReference.get());
System.gc();
System.runFinalization();
System.out.println(softReference.get());
上面的例子中两次都会正确输出"软引用",因为软引用只会在JVM认为内存不足时才会清理。
- 弱引用并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径。这就可以用来构建一种没有特定约束的关系,比如维护一种非强制性的映射关系,如果试图获取时对象还在,就使用他否则就重新实例化。软引用适合做缓存而弱引用适合存储元数据。
String str = new String("弱引用");
WeakReference weakReference = new WeakReference(str);
str = null;
System.out.println(weakReference.get());
System.gc();
System.runFinalization();
System.out.println(weakReference.get());
上面的例子中只会输出一次"弱引用",因为只要JVM运行GC发现有弱引用便会直接进行清理,并不会关注此时JVM内存是否充足。
- 幻象引用仅仅提供了一种确保对象被finalize之后做某些事情的机制,比如通常用来做所谓的post-Mortem清理机制,也有人利用幻想引用来监控对象的创建和销毁。
String str = new String("幻象引用");
ReferenceQueue referenceQueue = new ReferenceQueue();
PhantomReference phantomReference = new PhantomReference(str, referenceQueue);
str = null;
System.out.println(phantomReference.get());
System.gc();
System.runFinalization();
System.out.println(referenceQueue.poll() == phantomReference);
幻想引用在任何时候使用get方法都只会返回null,因为幻想引用总是不可达的。需要注意的是幻象引用必须与引用队列结合起来使用,幻想引用会在对象被释放(执行finalize后)时加入到引用队列之中,软引用与弱引用是在对象被垃圾回收之后会被加入到引用队列中。
不同的引用类型,主要体现的是对象不同的可达性状态和对垃圾收集的影响。
二、引用队列
我们在创建各种引用并关联到相应对象中时,可以选择是否需要关联引用队列,JVM会在特定时机将引用放入引用队列中,我们可以从队列中获取引用进行相关后续逻辑。尤其是幻想引用,get方法返回null,如果不指定引用队列基本就没有意义了。
Object counter = new Object();
ReferenceQueue refQueue = new ReferenceQueue<>();
PhantomReference<Object> p = new PhantomReference<>(counter, refQueue);
counter = null;
System.gc();
try {
// Remove 是一个阻塞方法,可以指定 timeout,或者选择一直阻塞
Reference<Object> ref = refQueue.remove(1000L);
if (ref != null) {
// do something
}
} catch (InterruptedException e) {
// Handle it
}
三、诊断JVM引用情况
如果怀疑应用存在引用导致的回收问题,可以通过指定JVM参数来排查诊断:
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintReferenceGC
四、Reachability Fence
按照Java规范如果一个对象没有指向强引用就符合垃圾收集的标准,有些时候对象并没有强引用,但是也许它得部分属性还在被使用,这样就导致诡异的问题,所以我们需要一个方法,在没有强引用的情况下通知JVM对象是在被使用的。
class Resource {
private static ExternalResource[] externalResourceArray = ...
int myIndex; Resource(...) {
myIndex = ...
externalResourceArray[myIndex] = ...;
...
}
protected void finalize() {
externalResourceArray[myIndex] = null;
...
}
public void action() {
try {
// 需要被保护的代码
int i = myIndex;
Resource.update(externalResourceArray[i]);
} finally {
// 调用 reachbilityFence,明确保障对象 strongly reachable
Reference.reachabilityFence(this);
}
}
private static void update(ExternalResource ext) {
ext.status = ...;
}
}
方法action的执行依赖于对象的部分属性,所以被保护了起来。否则如果我们在代码中像这样调用
new Resource().action()
可能就会出现问题,如果没有强引用指向我们创建出来的Resource对象,JVM对它进行finalize是完全合法的。这在新的异步编程模式下可能会非常有用,可以保障对象不被意外回收。
五、项目或者系统中实际使用到引用的案例我暂时还没有遇到,等遇到的时候再来补充吧。