在jdk 1.2及其以后,引入了强引用、软引用、弱引用、虚引用这四个概念。
1.强引用
StrongReference是Java的默认引用实现,它会尽可能长时间的存活于JVM内,当没有任何对象指向它时GC执行后将会被回收。
2.软引用
SoftReference在JVM报告内存不足的时候才会被GC回收,否则不会回收,正是由于这种特性软引用在内存敏感的caching和pooling中用处广泛。
此类的直接实例可用于实现简单缓存;该类或其派生的子类还可用于更大型的数据结构,以实现更复杂的缓存。只要软引用的指示对象是强可到达对象,即正在实际使用的对象,就不会清除软引用。例如,通过保持最近使用的项的强指示对象,并由垃圾回收器决定是否放弃剩余的项,复杂的缓存可以防止放弃最近使用的项。
@Test
public void softReference() {
Object referent = new Object();
/**
* 创建 SoftReference
*/
SoftReference<Object> softReference = new SoftReference<Object>(referent);
assertSame(referent, softReference.get());
referent = null;
System.gc();
/**
* soft references 只有在 jvm OutOfMemory 之前才会被回收, 所以它非常适合缓存应用
*/
assertNotNull(softReference.get());
}
3.弱引用
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
@Test
public void weakReference() {
Object referent = new Object();
/**
* 创建 WeakReference
*/
WeakReference<Object> weakReference = new WeakReference<Object>(referent);
assertSame(referent, weakReference.get());
referent = null;
System.gc();
/**
* 一旦没有指向 referent 的强引用, weak reference 在 GC 后会被自动回收
*/
assertNull(weakReference.get());
}
4.虚引用
虚引用对象,在回收器确定其指示对象可另外回收之后,被加入队列。虚引用最常见的用法是以某种可能比使用 Java 终结机制更灵活的方式来指派 pre-mortem 清除动作。
如果垃圾回收器确定在某一特定时间点上虚引用的指示对象是虚可到达对象,那么在那时或者在以后的某一时间,它会将该引用加入队列。
为了确保可回收的对象仍然保持原状,虚引用的指示对象不能被获取:虚引用的 get 方法总是返回 null。
与软引用和弱引用不同,虚引用在加入队列时并没有通过垃圾回收器自动清除。通过虚引用可到达的对象将仍然保持原状,直到所有这类引用都被清除,或者它们都变得不可到达。
PhantomReferences对象是在对象从内存中清除出去的时候才入队的。也就是说当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
好处:
1)它可以让我们准确地知道对象何时被从内存中删除, 这个特性可以被用于一些特殊的需求中(例如 Distributed GC, XWork 和 google-guice 中也使用 PhantomReference 做了一些清理性工作)。
2)它可以避免 finalization 带来的一些根本性问题, 上文提到 PhantomReference 的唯一作用就是跟踪 referent 何时被 enqueue 到 ReferenceQueue 中, 但是 WeakReference 也有对应的功能, 两者的区别到底在哪呢 ?
这就要说到 Object 的 finalize 方法, 此方法将在 gc 执行前被调用, 如果某个对象重载了 finalize 方法并故意在方法内创建本身的强引用, 这将导致这一轮的 GC 无法回收这个对象并有可能引起任意次 GC, 最后的结果就是明明 JVM 内有很多 Garbage 却 OutOfMemory, 使用 PhantomReference 就可以避免这个问题, 因为 PhantomReference 是在 finalize 方法执行后回收的,也就意味着此时已经不可能拿到原来的引用, 也就不会出现上述问题, 当然这是一个很极端的例子, 一般不会出现。
static class Entity {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize...");
}
}
@Test
public void phantomReferenceEntity() {
Entity referent = new Entity();
/**
* 创建 PhantomReference
*/
ReferenceQueue<Entity> queue = new ReferenceQueue<Entity>();
PhantomReference<Entity> phantomReference = new PhantomReference<Entity>(referent, queue);
referent = null;
System.gc();
assertNull(queue.poll());
try {
Thread.currentThread().sleep(3000);
System.gc();
System.out.println("after finalize...");
assertNotNull(queue.poll());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
5.可到达性
从最强到最弱,不同的可到达性级别反映了对象的生命周期。在操作上,可将它们定义如下:
1)如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达对象。新创建的对象对于创建它的线程而言是强可到达对象。
2)如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达对象。
3)如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
4)如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达对象。
5)最后,当不能以上述任何方法到达某一对象时,该对象是不可到达对象,因此可以回收此对象。
6.参考资料
http://www.iteye.com/topic/401478
http://blog.sina.com.cn/s/blog_8417aea80100skwx.html
http://blog.sina.com.cn/s/blog_667ac0360102e9f3.html