四种引用:强引用,软引用,弱引用,虚引用
为什么要引入不同引用的概念?之前由于内存小,对象就两种状态,要么被引用,要么被清除,随着内存的扩大,java充实了对象的状态,于是就有了这四种引用;
强引用
你的代码中绝大多数都是强引用。
如果一个对象,GC root可达,GC清理宁可抛出OOM异常终止程序,也不会清理掉它;
软引用
用于描述那些不是必需的对象,但由于内存充足可保留,适合用作缓存。那么GC什么时候能够清理掉软引用呢?
public class SoftReference<T> extends Reference<T> {
/**
* Timestamp clock, updated by the garbage collector
*/
static private long clock;
/**
* Timestamp updated by each invocation of the get method. The VM may use
* this field when selecting soft references to be cleared, but it is not
* required to do so.
*/
private long timestamp;
public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}
public SoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
}
注意这两个变量clock 和 timestamp;
在JVM发生GC时,也会更新clock的值,意味着clock会记录上次GC发生的时间点;
另一个变量timestamp,在软引用初始化时,会被初始化成clock,同时在get方法被调用时,也会更新timestamp的值;
所以如果一个SoftReference长时间没有被调用(即get方法没被调用)那么clock 和 timestamp之间会形成差值;
首先当GC时,会先检查该SoftReference所引用的对象是否还存活,即GC root是否可达;若不可达就会尝试回收引用对象,这里有几种回收策略,如LRUCurrentHeapPolicy;该种策略下,会对clock 和 timestamp差值与某个值进行比较,这个值的大小与上次GC后剩余空间大小正相关,也就是对空间剩余空间越大,对象存活越长;
弱引用
WeakReference:相对于软引用,它的生命周期更短,当发生GC时,如果扫描到一个对象只有弱引用,不管当前内存是否足够,都会对它进行回收。
还记得ThreadLocal里对它的的引用吗?ThreadLocalMap里的Entry继承的就是WeakReference,它将对ThreadLocal对象的引用设为弱引用,在检测到key为null之后会对entry进行清理再重新rehash,如getEntry,set,remove方法;所以我们一般这样使用:
private final static ThreadLocal memory = new ThreadLocal();
延长它的生命周期,并在需要的时候主动调用remove清理,防止OOM。
为什么使用弱引用?如上我们将memory = null,表示要删除存储的数据,则此时ThreadLocal对象只剩弱可达性,那么GC启动后,Entry就会被清理;如果是强引用,那么ThreadLocal对象仍是可达的就不会被清理,容易引发OOM;在WeakHashMap中也是此设计;
虚引用
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
PhantomReference它的get方法返回null;
虚引用的使用场景很窄,在JDK中,目前只知道在申请堆外内存时有它的身影。
申请堆外内存时,在JVM堆中会创建一个对应的Cleaner对象,这个Cleaner类继承了PhantomReference,当DirectByteBuffer对象被回收时,可以执行对应的Cleaner对象的clean方法,做一些后续工作,这里是释放之前申请的堆外内存。
虚引用存在的唯一作用就是当它指向的对象被回收后,虚引用本身会被加入到引用队列中,用作记录它指向的对象已被回收。
Reference
除了强引用外,其他几种都继承自Reference;
private T referent; /* Treated specially by GC */
volatile ReferenceQueue<? super T> queue;
Reference next;
transient private Reference<T> discovered;
private static Reference<Object> pending = null;
- referent:指所要引用的对象
- queue:ReferenceQueue队列,存放Reference
- next: Reference进队列呈链表结构
- discovered:被JVM使用,表示下一个要被处理的Reference对象
- pending:被JVM使用,当前被处理的Reference对象;
Reference里有个ReferenceHandler线程,优先级极高,负责轮询检查pending值,不为空则放入队列;我们可以利用这点对要清初的对象进行些操作,如下例:
public static void main(String[] args) {
final ReferenceQueue queue = new ReferenceQueue();
new Thread(){
@Override
public void run() {
try {
Reference reference = queue.remove();
System.out.println(reference + "被回收");
} catch (InterruptedException e) {
}
}
}.start();
Object ob = new Object();
Reference root = new WeakReference(ob, queue);
System.out.println(root);
ob = null;
System.gc();
}
gc()启动后,弱引用对象ob要被清除于是被赋予到penging上,ReferenceHandler线程会将它放入到我们的queue当中,我们从queue取出进行打印操作;