背景:
每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过“引用”。 java不同于c/c++,它不需要程序员自已来管理内存(分配,释放内存),java 会自己来管理内存,比如销毁某些不再被使用的对象。这些操作都是在一个后台线程默默进行(Garbage Collector Thread),也就是垃圾收集器线程,根据jvm实现的策略来释放对象内存。但是程序编写者却无法控制这个后台线程,无法让它在你想要的时候开始释放内存,销毁对象,按照你的规定来销毁那些对象,释放内存,这些都是都jvm自己控制的。但是随着 java.lang.ref这个包下的类的引进,程序员拥有了一点点控制你创建的对象何时释放,销毁的权利,当然只是一点点。
四种引用类型
在 JDK1.2 之前,Java中的定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称为这块内存代表着一个引用。
Java 中的垃圾回收机制在判断是否回收某个对象的时候,都需要依据“引用”这个概念。
但是,JDK1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。
所以在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。
强引用:
只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了(代码优化:某个大对象使用完后,如果后面的业务耗时久并且不会使用该对象,可以考虑赋值null)
在 JDK1.2 之后,用java.lang.ref.FinalReference类来表示软引用。
/**
* Final references, used to implement finalization
*/
class FinalReference<T> extends Reference<T> {
public FinalReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
软引用:
软引用是用来描述一些“还有用但是非必须”的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
但实际上,软引用的回收策略在不同的JVM实现会略有不同,JVM不仅仅只会考虑当前内存情况,还会考虑软引用所指向的referent最近的使用情况和创建时间来综合决定是否回收该referent。软引用保存了两个变量:
timestamp:每次调用get方法都会更新时间戳。JVM可以利用该字段来选择要清除的软引用,但不是必须要这样做。
clock:时间锁,由垃圾收集器更新。
因此,任何GC都可以使用这些字段并定义清除软引用的策略,例如:最后清除最近创建的或最近使用的软引用。
在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
/**
* 软引用对象由垃圾收集器根据内存需要决定是否清除。软引用经常用于实现内存敏感的缓存。
*
* 假如垃圾收集器在某个时间确定对象是软可达的,此时它可以选择原子地清除
* 指向该对象的所有软引用,以及从该对象通过强引用链连接的其他软可达对象的所有软引用。
* 与时同时或者之后的某个时间,它会将注册了reference queues的新清除的软引用加入队列。
*
* 在虚拟机抛出OutOfMemoryError异常之前,将保证清除对软可达对象的所有软引用。
* 不过,并没有对清除软引用的时间以及清除顺序施加强制约束。
* 但是,鼓励虚拟机实现偏向不清除最近创建或最近使用的软引用。
*
* 该类的直接实例可用于实现简单的缓存。
* 该类或其派生子类也可用于更大的数据结构以实现更复杂的高速缓存。
* 只要软引用的引用对象还是强可达的,即还在实际使用中,软引用就不会被清除。
* 因此,复杂的高速缓存可以通过持有对最近使用缓存对象的强引用来防止其被清除,
* 而不常使用的剩余缓存对象由垃圾收集器决定是否清除。
*/
public class SoftReference<T> extends Reference<T> {
// 时间锁,由垃圾收集器更新。
static private long clock;
// 每次调用get方法都会更新该时间戳。JVM可能会在选择要清除的软引用时使用该字段,
// 但这不是强制必须的。
private long timestamp;
// 返回对象的引用。如果该引用对象已经被程序或者垃圾收集器清除,则返回null。
// 把最近一次垃圾回收时间赋值给timestamp
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
}
弱引用:
弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。
当一个对象没有被强引用或者软引用连接,但被弱引用连接时,则处于弱可达状态。只要发生GC,弱可达的对象就会被清除,弱引用无法阻止自身被回收,同时会把弱引用加入到注册的引用队列中(如果存在的话)。弱引用对GC几乎是没有影响的,它不影响对应的referent被终结(finalized)和回收(reclaimed)。因此,弱引用最常用于实现规范化映射(canonicalizing mappings),例如哈希表,如果它们在程序中未被引用,则其键和值将从映射中删除。
在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。
/**
* 弱引用对象不能阻止自身的引用被回收。
* 弱引用常用于实现规范化映射(对象实例可以在程序的多个地方同时使用)。
*
* 假如垃圾收集器在某个时间点确定对象是弱可达的。那时它将原子地清除对该对象的所有弱引用
* 以及该引用通过强引用或者软引用连接的所有其他弱可达对象的所有弱引用。
* 同时,它将表明前面所指的所有弱可达对象都可以执行finalize方法。
* 与此同时或之后某一个时间,它将注册了reference queues的那些新清除弱引用加入队列。
*/
public class WeakReference<T> extends Reference<T> {
// 创建没有注册ReferenceQueue的弱引用
public WeakReference(T referent) {
super(referent);
}
// 创建注册了ReferenceQueue的弱引用
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
虚引用:
虚引用是所有引用类型中最弱的一种。一个对象是否关联到虚引用,完全不会影响该对象的生命周期,也无法通过虚引用来获取一个对象的实例。虚引用和弱引用的回收机制差不多,都是可以被随时回收的。
为对象设置一个虚引用的唯一目的是:它的构造方法必须强制传入ReferenceQueue,因为在jvm回收前(重点:软引用和弱引用都是回收后)JVM自动把虚引用对象本身(PhantomReference对象)加入到ReferenceQueue中,表明该reference指向的referent被回收。然后可以通过去queue中取到reference,可以通过这个来做额外的清理工作。可以用虚引用代替对象finalize方法来实现资源释放,这样更加灵活和安全。还有一点就是PhantomReference.get()方法永远返回空,不管对象有没有被回收。
在 JDK1.2 之后,用 java.lang.ref.PhantomReference 来表示弱引用。
/**
* 虚引用对象在被垃圾收集器检查到后加入reference queues队列,否则会被回收。
* 虚引用最常用于实现比Java finalization机制更灵活的安排额外的清理工作。
*
* 如果垃圾收集器在某个时间点确定虚引用对象是虚可达的,那么在那个时间或之后某个时间它会将引用加入reference queues队列。
*
* 为了确保可回收对象保持不变,虚引用的引用无法使用:虚引用对象的get方法始终返回null。
*
* 与软引用和弱引用不同,当虚引用加入reference queues队列后垃圾收集器不会被自动清除。
* 只通过虚引用可达的对象将保持不变,直到所有此类引用都被清除或自已变为不可达。
*/
public class PhantomReference<T> extends Reference<T> {
// 由于不能通过虚引用访问对象,因此此方法始终返回null。
public T get() {
return null;
}
// 使用空ReferenceQueue队列创建一个虚引用没有意义:它的get方法总是返回null,
// 并且由于它没有注册队列,所以也不会被加入队列有任何清理前的预处理操作。
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}