深入理解Java中的引用(二)——强软弱虚引用
在上一篇文章中介绍了Java的Reference类,本篇文章介绍他的四个子类:强引用、软引用、弱引用、虚引用。
强引用(StrongReference)
强引用是我们在代码中最普通的引用。示例代码如下:
Object o = new Object(); // 强引用
在JVM的GC算法中,如果一个对象具有强引用,那么JVM宁可抛出Out of Memory错误,垃圾回收器也不会去回收这个对象。
当在代码里显示的写o = null,或者该对象的引用作用域是在一个函数里,代码如下,当线程调用完test,就会退出方法栈,引用不存在,垃圾回收器才会在某个时刻回收Object对象。
public void test(){
Object o = new Object(); // 强引用
}
软引用(SoftReference)
如果一个对象有一个软引用,那么在内存足够的情况下,该对象就不会被垃圾回收器回收。网上有很多资料说软引用只会在内存空间不够用的情况下对象才会被回收。 那么什么时候才是内存不够用呢?
首先看一下SoftReference类的源码可以看到有两个字段。这两个字段的作用已经标注,这与JVM GC有什么关系呢?
/**
* 记录最近一次被GC的时间。
*/
static private long clock;
/**
* 每次调用get方法的时候更新
* 记录当前Reference最近一次被访问的时间
*/
private long timestamp;
一起看一下HotSpot的源码,对于软引用的回收策略见下面should_clear_reference函数。
// The oop passed in is the SoftReference object, and not
// the object the SoftReference points to.
bool LRUMaxHeapPolicy::should_clear_reference(oop p,
jlong timestamp_clock) {
jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
assert(interval >= 0, "Sanity check");
// The interval will be zero if the ref was accessed since the last scavenge/gc.
if(interval <= _max_interval) {
return false;
}
return true;
}
上述代码中interval表示当前引用存活了多久。他的值就是对应上述java代码中的clock与timestamp相减。interval与_max_interval比较,如果大于 _max_interval,那么就和弱引用一样处理,如果小于就当做强引用处理。_max_interval的赋值函数如下:
// Capture state (of-the-VM) information needed to evaluate the policy
void LRUMaxHeapPolicy::setup() {
size_t max_heap = MaxHeapSize;
max_heap -= Universe::get_heap_used_at_last_gc();
max_heap /= M;
_max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
assert(_max_interval >= 0,"Sanity check");
}
通过源码可见首先是max_heap减去上次GC之后剩余堆大小,如果上次GC之后还有很多剩余空间,说明内存空间不够用了,那么max_heap的值就越小,相应_max_interval也越小,软引用就越可能被回收。
软引用的一个作用是实现内存敏感的高速缓存。比如浏览器的后退按钮,
(1)如果网页浏览结束就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建。
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出。
通过软引用可以解决该问题
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 还没有被回收器回收,直接获取
}else{
prev = new Browser(); // 由于内存吃紧,所以对软引用的对象回收了
sr = new SoftReference(prev); // 重新构建
}
弱引用(WeakReference)
只具有弱引用的对象生命周期更短。当垃圾回收器发现了只有弱引用的对象时候,无论内存空间是否足够,都会被GC回收。当你偶尔需要引用某个对象,随时能获取该对象,但是不想介入该对象的生命周期的时候,就可以使用弱引用, 因为弱引用不会对对象的垃圾回收判断产生附加的影响。
当弱引用绑定的对象被垃圾回收的时候,JVM会把这个弱引用加入到相关联的ReferenceQueue中。
这里抛出两个问题:
(1)弱引用什么时候会被加入到ReferenceQueue中,由什么决定的呢?
(2)如果绑定的对象GC之后存活了下来,弱引用怎么知道这个对象的新地址呢?
第一种情况,GC扫描到只存在弱引用的时候就会把它放到链表里。还有第二种情况:一个对象既有强引用又有弱引用的情况
下面通过图片来解释上面两个问题:
image.png
上图表示C同时存在两个引用:强引用A和弱引用B。
第一种情况:GC先扫描到A
这种情况下GC同时会扫描到C,A和C都会搬到Survivor区。然后扫描到B,发现B引用的C搬到到了新的Survivor,这个时候就把B也搬到Survivor,并把C的新地址更新到B,结果如下:
image.png
第二种情况:GC先扫描到B
GC还是会先把B放到ReferenceQueue中,由于C还是存活的,所以B会被搬到Survivor中。然后扫描到A,A和C都会搬到Survivor中,GC结束的时候B所指向的对象就不对了,如下图所示
image.png
该情况下会重新遍历ReferenceQueue,发现绑定的对象依然存活,C‘ 的指针是指向C的,于是就把B再指向C就可以了,同时因为C依然存活,把B从ReferenceQueue中移除。新的地址空间如下图所示:
image.png
具体过程可以查看海纳知乎专栏。
虚引用(PhantomReference)
虚引用不会对对象的垃圾回收有任何附加影响,他与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。查看他的构造方法可以看到必须与一个ReferenceQueue绑定,而且他的get方法返回的一直是null
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue super T> q) {
super(referent, q);
}
虚引用主要用在跟踪对象垃圾回收的状态。具体应用会在在下一节讲到DirectByteBuffer 与ThreadLoal回收的时候详细分析。
总结
关于强引用、软引用、弱引用与虚引用在垃圾回收时的区别可以用下图表示:
image.png
下图总结了四种引用在其他方面的区别:
image.png