这篇文章主要介绍四大引用,java中引用分四种:强引用,弱引用,软引用,虚引用。
而在Java项目中,百分之九十五都是强引用,软引用偶尔在缓存中用到,而弱和虚很少用到。下面我们一个个详细的讲一下:

关于引用的一些知识点我们可以在jdk8中文手册中看一下:

下面一一说下四种引用:
强引用
当内存不足时,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收!
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态。它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到。JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
对于一个普通的对象,如果没有其它的引用关系,只要超过了引用的作用域或者显式的将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当然了具体的回收时机还是要看垃圾收集策略)
一般我们常用的为对象赋值就是强引用,下面用代码写个demo:
public static void main(String[] args) throws Exception {
Object o1 = new Object();
Object o2 = o1;
System.out.println(o1);
System.out.println(o2);
System.out.println(o1==o2);
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(o2);
}
上面的代码我们创建一个o1,然后又创建一个o2并且给其赋值o1.这个时候o1,o2指向一个对象。两个也是全等的到这没问题。
但是后来我们将o1赋值为null。这个时候再输出o1.o2,发现o1确实是null了,但是o2的指向还没变。而到这个时候,因为o2指向了这个对象,那么哪怕爆发oom了,这个对象也不会被回收!

软引用
什么是软引用?这个是比强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现。可以让对象豁免一些垃圾回收。
强引用是死了崩了都不回收,而软引用是内存足够就不收,但是内存不够了就会回收!
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用。内存够用的时候就保留,不够用就回收。
下面我们先去看看手册中这个软引用类如何使用:

这个手册上介绍的比较清楚,先有这么个对象,然后我们用这个类创建这个对象的软引用。然后用get方法获取这个引用的对象。下面我们用代码试试:
/**
*
* @author lisijia
*
*/
public class SoftRenferenceDemo {
public static void softE() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<Object>(o1);
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(softReference.get());
}
public static void softNE() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<Object>(o1);
o1 = null;
try {
byte[] bytes = new byte[30*1024*1024];
} catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(o1);
System.out.println(softReference.get());
}
}
public static void main(String[] args) {
softE();
softNE();
}
}
上面两个方法第一种是测试内存够用,第二种是测试内存不够用。这里需要注意!!!!第二种方式创建了一个30m左右的大对象。要想测试出效果要启动的时候设置最大空间小于30m的。如下设置方式:

然后我们再运行上面的代码:

注意代码的执行:先执行够用的那个方法,因为内存够用,所以哪怕o1设置为null,其中虚引用指向的对象没有被清除(同样因为内存够用不会触发gc,所以手动调用gc)。
而第二个方法因为内存只有5m,但是创建一个30m的对象所以肯定是内存不够用了。于是虚引用的指向也被清理了。所以是null。
弱引用
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象。
下面依然代码测试:
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> weakReference = new WeakReference<Object>(o1);
System.out.println(o1);
System.out.println(weakReference.get());
o1 = null;
System.gc();
System.out.println(weakReference.get());
}
运行结果是gc后weakReference.get()也是null。
什么情景下需要软引用和弱引用?
脱离实际场景谈技术纯属耍流氓!那么具体在什么场景下使用软引用或者弱引用呢?打个比方:有一个应用需要读取大量的本地图片。
如果每次读取图片都从硬盘中读取会有严重的性能问题,但是一次性全部加载到内存中又可能造成内存溢出。
此时使用软引用就可以解决这个问题。设计思路如下:
用一个HashMap来保存图片的路径和相应图片对象关联的软/弱引用之间的映射关系。在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效的避免了OOM的问题。
WeakHashMap是什么?
我们可以先去jdk文档中查看:

然后下一步就是代码测试,这里我们用常用的HashMap和WeakHashMap对比测试:
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<Integer, String>();
Integer key = new Integer(1);
String value = "1";
map.put(key, value);
System.out.println(map);
key = null;
System.gc();
System.out.println(map);
System.out.println("==================================================");
WeakHashMap<Integer, String> weakHashMap = new WeakHashMap<Integer, String>();
Integer key1 = new Integer(2);
String value1 = "2";
weakHashMap.put(key1, value1);
System.out.println(weakHashMap);
key1 = null;
System.gc();
System.out.println(weakHashMap);
}
首先正常来讲上面的代码除了map的类型不一样其余的代码都是一样的。然后运行结果是不同的,看图:

注意一下这个结果weakHashMap经过gc以后变成了null。变成null的原因是这个key没有了,weakHashMap中,存进去的key不是强引用而是弱引用。而这个强引用指向不在了,gc的时候弱引用是会被清理的。所以这个map的key被清理了,也就为空了。
虚引用
虚引用是四大引用的最后一个。需要java.lang.ref.PhantomReference类来实现。
顾名思义,phantom是幽灵的意思,也就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么他就和没有任何引用一样。在任何时候都可能被垃圾回收器回收。它不能单独使用也不能通过它访问对象。虚引用必须和引用队列(ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态。它仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。
PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更加灵活的回收操作。
换句话说,设置虚引用关联的唯一目的就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。
java技术允许使用finalize()方法在垃圾收集器将对象从内存中清出去之前做必要的清理工作。
下面依然是代码展示(因为虚引用本身获取就是null,所以这里用弱引用来测试):
public static void main(String[] args) throws Exception{
Object o1 = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
WeakReference<Object> weakReference = new WeakReference<Object>(o1,queue);
System.out.println(o1);
System.out.println(weakReference.get());
System.out.println(queue.poll());
System.out.println("==========================");
o1 = null;
System.gc();
TimeUnit.SECONDS.sleep(1l);
System.out.println(o1);
System.out.println(weakReference.get());
System.out.println(queue.poll());
}
运行结果是最后一个队列的输出是有东西的,说明o1在被gc回收后,进入了队列。

其实我们可以把弱引用换成虚引用,但是这样只能确定被回收后队列里有东西了,get这步就看不到了。我偷个懒,上面代码单纯的把弱引用换成虚引用。如下图:

其实这个引用队列我们可以理解为spring 的aop的后置通知,白话理解死之前做点什么。
四大引用总结
强引用(默认使用):死了也不回收,哪怕oom。
软引用:有空间就不收,没空间就回收(地主家没余粮)。
弱引用:不管有没有空间都回收。
虚引用:和回不回收不搭嘎。必须和引用队列配合使用,只能死之前留个遗言。

上图中,灰色部分都会被回收(其中有两个互相强引用的是引用不可达)。
本篇笔记就记到这里,主要讲了四种引用的用法和区别。如果稍微帮到你了记得点个喜欢点个关注。也祝大家工作顺顺利利!
网友评论