年前看到一个关于软引用弱引用相关的面试题,自己之前也就听说过弱引用的概念。所以最近把相关ref包下的源码看了下,也找了点资料,把这部分内容总结一下,如下是这篇博客的大纲
一.引入
二.几种引用的概念和使用
三.几种引用的使用场景和注意事项
四.JDK中的ReferenceQueue
五.总结
一.引入
在java中,我们肯定会碰到OOM(OutOfMemory)问题。为什么会出现这个问题,怎么解决呢,今天这个内容就给出了一种方案来说明,解决
二.几种引用的概念和使用
从JDK1.2之前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象,也就是说,只有对象时可触及状态(reachable),程序
才能使用它,从1.2开始,把对象的引用分为四个级别,由高到低依次是:强引用,软引用,弱引用,虚引用。从而使程序能够更灵活地控制对象
的生命周期(1.可以通过代码的方式决定某些对象的生命周期;2.有利于JVM进行垃圾回收)。关于引用相关的类都在java.lang.ref包下,如下图
所示:
2.1强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机
宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
强引用在日常代码中普遍存在,比如下面代码test方法内部有三个强引用list,str,objArray,这三个引用保存在栈中。
而真正的应用内存(new出的对象)保存在堆中。
public void test() {
List<String> list = new ArrayList<String>();
String str = "S1amDuncan";
Object[] abjArray = new Object[10000];
}
只要对象有强引用与之关联,那么JVM就不会回收这个对象,比如第三个objArray,
JVM宁愿抛出OutOfMemoryError也不会回收这个对象。不过需要注意的是当test方法使用完后,list,str,objArray就不存在了,
所以他们指向的对象会被JVM回收。如果想中断强引用和某个对象之间的关联,可以显示的将引用赋值为null,这样,
JVM就会在合适的时间回收该对象,这个在JDK源码中有很多地方应用到了,
我们平时在阅读JDK源码的时候可能会看到如下代码注释,就是这样做的。
// Let gc do its work
2.2软引用(SoftReference)
如果一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存不够了,就会回收这些对象的内存,只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可以用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引起的对象被垃圾回收期回收,Java虚拟机就会把这个软引用加入到与之相关联的引用队列中。使用示例如下
public class TestSoft {
public static void main(String[] args) {
SoftReference<String> sr = new SoftReference<String>(new String("hello"));
System.out.println(sr.get());
}
}
2.3弱引用(WeakReference)
弱引用与软引用的区别在于:弱引用的对象(仅持有弱引用)拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,无论当前内存空间是否足够,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenmceQueue)联合使用,如果弱引用所引起的对象被垃圾回收器回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。使用示例如下
public class TestWeak {
public static void main(String[] args) {
WeakReference<String> wr = new WeakReference<String>(new String("hello"));
System.out.println(wr.get());
System.gc(); //通知JVM的gc进行垃圾回收
System.out.println(wr.get());
}
}
2.4虚引用(PhantomReference)
虚引用,顾名思义,就是形同虚设,与其他几种引用不同,虚引用并不会决定对象的声明周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别就在于:虚引用必须和引用队列(ReferenceQueue)联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以再所引用的对象的内存被回收之前采取必要的。使用示例如下
public class TestPhantom {
public static void main(String[] args) {
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
System.out.println(pr.get());
}
}
以上内容需要注意这个概念,一个对象并不是只能持有一种类型的引用,可能强软弱虚引用都有。
三.几种引用的使用场景和注意事项
3.1强引用
关于强引用的使用场景就不说了,基本日常工作中都是用到的这个。
3.2软引用
软引用可以用来实现内存敏感的高速缓存,例如浏览器的后退按钮,按后退时,这个后退需要显示的网页内容是重新进行请求还是从缓存中取出呢,这就要看具体的实现策略了。
(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); // 重新构建
}
这样就能很好的解决实际问题。
3.3弱引用
当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这个时候就可以使用弱引用,这个引用不会在对象的垃圾回收判断中产生任何附加的影响
如下
public class ReferenceTest {
private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<VeryBig>();
public static void checkQueue() {
Reference<? extends VeryBig> ref = null;
while ((ref = rq.poll()) != null) {
if (ref != null) {
System.out.println("In queue: " + ((VeryBigWeakReference) (ref)).id);
}
}
}
public static void main(String args[]) {
int size = 3;
LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<WeakReference<VeryBig>>();
for (int i = 0; i < size; i++) {
weakList.add(new VeryBigWeakReference(new VeryBig("Weak " + i), rq));
System.out.println("Just created weak: " + weakList.getLast());
}
System.gc();
try { // 下面休息几分钟,让上面的垃圾回收线程运行完成
Thread.currentThread().sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
checkQueue();
}
}
class VeryBig {
public String id;
// 占用空间,让线程进行回收
byte[] b = new byte[2 * 1024];
public VeryBig(String id) {
this.id = id;
}
protected void finalize() {
System.out.println("Finalizing VeryBig " + id);
}
}
class VeryBigWeakReference extends WeakReference<VeryBig> {
public String id;
public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> rq) {
super(big, rq);
this.id = big.id;
}
protected void finalize() {
System.out.println("Finalizing VeryBigWeakReference " + id);
}
}
输出结果为:
Just created weak: com.javabase.reference.VeryBigWeakReference@1641c0
Just created weak: com.javabase.reference.VeryBigWeakReference@136ab79
Just created weak: com.javabase.reference.VeryBigWeakReference@33c1aa
Finalizing VeryBig Weak 2
Finalizing VeryBig Weak 1
Finalizing VeryBig Weak 0
In queue: Weak 1
In queue: Weak 2
In queue: Weak 0
3.4虚引用
这个在实际中基本很少使用,清楚2.4相关的概念即可。
四.JDK中的ReferenceQueue
在上面中多次提到了ReferenceQueue,可能对这个类的概念还不太清晰,那么接下来对ReferenceQueue做一个简单的讲解。
举个栗子:你从超市买了各种小工具放在在房间不同的柜子里,每个柜子有一把钥匙。如果发现有的小工具很有用,你就一直留着,如果发现有的没用,就把这个丢到垃圾桶,由清洁工人收走,如果这个工具被扔到垃圾桶,想要再把它捡回来使用就不可能了。但是有时候,可能碰到这个工具虽然占着你柜子的空间,而且暂时没用,但是立刻扔掉也不划算,因为将来可能还会派上用场,对于这种可有可无的物品,一种方法就是,如果柜子空间足够,就先把它保留在柜子里,如果柜子里空间不够,再扔掉这些可有可无的物品。这个时候我们把这种可有可无需要暂时保留的物品(对象)对应柜子的钥匙(引用)放在一个包(引用队列)里。如果需要用到就去这个包里取,再打开对应的柜子看是否这个物品还在,如果不在就说明可能之前空间不足或者其他原因被人丢掉了。
五.总结