Java对引用的定义
无论是通用引用计数算法判断对象的引用数据,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。
在JDK1.2之前,Java中的引用定义很传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。
这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,对于如何描述一些“食之无味,弃之可惜”的对象就显得无能为力。我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存中,如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。
在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引
用4种,这4种引用强度依次逐渐减弱。强引用就是指在程序代码中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
1、强引用
特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。
当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。
2、软引用
软引用通过SoftReference
类实现。 软引用的生命周期比强引用短一些。只有当JVM认为内存不足时,才会去试图回收软引用指向的对象,即JVM会确保在抛出OutOfMemoryError之前,清理软引用指向的对象。
软引用可以和一个引用队列(ReferenceQueue
)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。
应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
3、弱引用
弱引用通过WeakReference
类实现,弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue
)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
应用场景:弱应用同样可用于内存敏感的缓存。
4、虚引用
虚引用也叫幻象引用,通过PhantomReference
类来实现。
无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。 如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。
应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。
垃圾回收代码测试
public class TestReference {
private static List
在笔记本上运行效果如下,可以看到默认的JVM可用内存很大,垃圾回收时在内存空间足够用的情况下,软引用对象占用的空间不会被回收。
我是软引用
231M(free) / 3641M(max)
[B@60e53b93
[B@5e2de80c
[B@1d44bcfa
[B@266474c2
[B@6f94fa3e
[B@5e481248
[B@66d3c617
[B@63947c6b
[B@2b193f2d
[B@355da254
softReference.get() : [B@4dc63996
[B@60e53b93
[B@5e2de80c
[B@1d44bcfa
[B@266474c2
[B@6f94fa3e
[B@5e481248
[B@66d3c617
[B@63947c6b
[B@2b193f2d
[B@355da254
------------
我是弱引用
219M(free) / 3641M(max)
null
null
null
null
null
null
null
null
null
null
------------
null
true
Process finished with exit code 0
现在设置-Xms2M -Xmx15M,然后重新运行,效果如下:
我是软引用
1M(free) / 14M(max)
[B@60e53b93
[B@5e2de80c
[B@1d44bcfa
[B@266474c2
[B@6f94fa3e
[B@5e481248
[B@66d3c617
[B@63947c6b
[B@2b193f2d
[B@355da254
softReference.get() : [B@4dc63996
null
null
null
null
null
null
null
null
null
null
------------
我是弱引用
2M(free) / 14M(max)
null
null
null
null
null
null
null
null
null
null
------------
null
true
Process finished with exit code 0
软引用的实际应用
如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
比如在图片加载框架中,通过软引用来实现内存缓存。
//实现图片异步加载的类
public class AsyncImageLoader {
//以Url为键,SoftReference类型为值,建立缓存HashMap键值对。
private Map> mImageCache = new HashMap>();
//实现图片异步加载
public Drawable loadDrawable(final String imageUrl, final ImageCallback callback) {
//查询缓存,查看当前需要下载的图片是否在缓存中
if(mImageCache.containsKey(imageUrl)) {
SoftReference softReference = mImageCache.get(imageUrl);
if (softReference.get() != null) {
return softReference.get();
}
}
final Handler handler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
//回调ImageCallbackImpl中的imageLoad方法,在主线(UI线程)中执行。
callback.imageLoad((Drawable)msg.obj);
}
};
/*若缓存中没有,新开辟一个线程,用于进行从网络上下载图片,
* 然后将获取到的Drawable发送到Handler中处理,通过回调实现在UI线程中显示获取的图片
*/
new Thread() {
public void run() {
Drawable drawable = loadImageFromUrl(imageUrl);
//将得到的图片存放到缓存中
mImageCache.put(imageUrl, new SoftReference(drawable));
Message message = handler.obtainMessage(0, drawable);
handler.sendMessage(message);
};
}.start();
//若缓存中不存在,将从网上下载显示完成后,此处返回null;
return null;
}
//定义一个回调接口
public interface ImageCallback {
void imageLoad(Drawable drawable);
}
//通过Url从网上获取图片Drawable对象;
protected Drawable loadImageFromUrl(String imageUrl) {
try {
return Drawable.createFromStream(new URL(imageUrl).openStream(),"debug");
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
}