JAVA的四种引用
引子
在mybatis的缓存的cache的包里种有两个特别的缓存,SoftCache,WeakCache类
而这两个类的实现原理就是使用了java的软引用和虚引用实现的功能,那么这两个缓存有什么功能呢,这就要介绍一下java的四种引用.
一.为什么要有四种引用
首先要谈到JAVA的四种引用,就不得不说java的GC(垃圾回收)机制,在1.2版本以前我们只有一种引用方式——强引用即Object ob = new Object(),
GC机制不会回收仍然被引用的对象,而是一般针对的是没有被引用的对象(不可到达对象),当GC线程发现有对象没有被引用时便会回收这个对象避免内存溢出
但是这里就产生了一些小问题:强引用的方式让JVM即使在内存溢出的情况宁愿报错也不会去回收强引用的对象
有些对象对于我们来说是非必要但也可能是有用的对象(例如缓存),如果我们要用它就必须有他的引用,但是我们不用的时候他又会占用一定的内存而且由于强引用的存在一旦出现内存溢出的可能,这种食之无味弃之可惜的对象又不会被GC机制回收那么对于程序的运行是较大的弊端,除此之外有时候我们可能希望在回收某些对象时能够做出一些额外处理,因此在jdk1.2的版本中加入了一个java.lang.ref包提供了引用对象类和引用队列, 支持在某种程度上与垃圾回收器之间的交互.。
二.四种引用以及ReferenceQueue,WeakHashMap
1.五种可达到性
这五种可到达性反映了对象的声明周期
强可达到性: 如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达 对象。新创建的对象对于创建它的线程而言是强可到达对象
软可达到性: 如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达 对象
弱可达到性: 如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达 对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
虚可达到性: 如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达 对象。
不可达到性: 最后,当不能以上述任何方法到达某一对象时,该对象是不可到达 对象,因此可以回收此对象
2.四种引用:
强引用: 创建对象时的直接引用例如:Object o = new Object(),属于强可到达性;GC不会回收这种强引用对象,即使在内存溢出时宁愿报错也不会回收.
软引用:利用SoftReference实现的引用对象(类似于包装),同时该对象属于软可到达性(即无强引用)时,GC机制会在保证虚拟机抛出 OutOfMemoryError 之前清除软引用对象,同时如果有引用队列引用会加入队列.
弱引用:利用WeakReference实现的引用对象(类似于包装),, 同时该对象属于弱可到达对象时,一旦GC线程在执行期间确定了该对象为弱引用对象,那么不论是否内存溢出都会会执行GC机制,同时如果有引用队列引用会加入队列.
虚引用: 利用PhantomReference实现的引用对象(类似于包装),同时该对象属于虚可到达对象时,如果有队列便会加入队列,但是特点在于被虚引用的对象的生命周期不会受到印象,由于这种引用主要是用于接收当对象被回收时的通知,因此无法利用虚引用获取到该对象,
3ReferenceQueue和WeakHashMap
ReferenceQueue: ReferenceQueue,一种队列,用于在对象被GC后,JVM会将该对象的引用(包装)放入到ReferenceQueue对象中(由于线程的缘故时机有点迷),利用这个队列我们可以对被回收的对象再进行一些其他的额外处理
WeakHashMap:一种基于Hash表的Map于一般的HashMap有差不多能力, 但是这里键是一个弱键,当某个键不再正常使用时(即该键只被弱引用),将自动移除其条目,同时这里的映射关系不会影响GC,因此带键被回收时这个映射关系也就断开了.
4.各种引用以及队列和MaP的使用的使用
首先三种软,弱,虚引用时继承于抽象类Reference提供了四个方法:
引用队列ReferenceQueue:
软引用的使用:
public class Referance {
public static void main(String[] args) {
System.out.println("开始");
Target target = new Target();
//输出对象
System.out.println("对象:"+target);
//引用队列
ReferenceQueue<Target> queue = new ReferenceQueue<>();
//包装软引用对象和传入队列
SoftReference<Target> sr = new SoftReference<>(target,queue);
//改变可到达性
target = null;
System.gc();
//System.out.println(sr.isEnqueued());
if (sr.isEnqueued()) {
System.out.println("已经添加到队列中");
System.out.println("获取队列中的引用:"+queue.poll());
}else {
System.out.println("强引用:"+target);
System.out.println("利用软引用获得对象"+sr.get());
}
}
}
class Target{
}
弱引用的使用:
public static void main(String[] args) {
System.out.println("开始");
Target target = new Target();
//输出对象
System.out.println("对象:"+target);
//引用队列
ReferenceQueue<Target> queue = new ReferenceQueue<>();
//包装软引用对象和传入队列
WeakReference<Target> sr = new WeakReference<>(target,queue);
//改变可到达性
target = null;
System.gc();
//可以看到由于线程的缘故加入队列的时机并不确定
System.out.println(sr.isEnqueued());
if (sr.isEnqueued()) {
System.out.println("已经添加到队列中");
System.out.println("获取队列中的引用:"+queue.poll());
}else {
System.out.println("强引用:"+target);
System.out.println("利用软引用获得对象"+sr.get());
}
}
虚引用:
public class Referance {
public static void main(String[] args) {
System.out.println("开始");
Target target = new Target();
//输出对象
System.out.println("对象:"+target);
//引用队列
ReferenceQueue<Target> queue = new ReferenceQueue<>();
//包装软引用对象和传入队列
PhantomReference<Target> sr = new PhantomReference<>(target,queue);
//这里即使没有加入队列,也看看出虚引用是不能够获取到对象的,其只是作为一个通知的作用
System.out.println("利用虚用获得对象"+sr.get());
//改变可到达性
target = null;
System.gc();
//可以看到由于线程的缘故加入队列的时机并不确定
System.out.println(sr.isEnqueued());
if (sr.isEnqueued()) {
System.out.println("已经添加到队列中");
System.out.println("获取队列中的引用:"+queue.poll());
}else {
System.out.println("强引用:"+target);
System.out.println("利用虚用获得对象"+sr.get());
}
}
}
WeakHashMap的使用:
public class Test2 {
public static void main(String[] args) {
Map<Object, Object> map = new WeakHashMap<>();
for(int i=0;i<1000000;i++) {
Object object = new Object();
//Map
map.put( new Object(),object);
}
//利用线程查看剩余的引用
Thread thread = new Thread(()-> {
//由于GC线程的缘故,剩余的对象的数据并不是连贯的
while(map.isEmpty()!=true) {
System.out.println("剩余对象:"+map.size());
}
});
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
}
利用软引用建立简单的缓存机制
首先,对于缓存我们基本是以HashMap的形式进行的缓存机制,但是这种缓存机制有一个弊病那就是要人为的控制清除缓存
(这里用WeakReference容易体现一点内存溢出时的感觉,如果愿意也可去设置java的堆内存大小来实现内存溢出时的操作):
public static void main(String[] args) {
Map<Object, Object> map = new HashMap();
Object object = new Object();
WeakReference<Object> oReference = new WeakReference<Object>(object);
map.put( "Test", object);
object = null;
System.gc();
//map依然可以获取该对象
System.out.println(map.get("Test"));
}
即使object对象不在是强引用,但是由于HashMap中的键值仍然存在,那么GC便不会回收object对象,除非人为清空否则这部分的内存依然被占用,但我们会希望GC在发现内存快要溢出的时候就对自动清空这个缓存,因此我们可以利用软引用实现这种机制:
public class Test8 {
public static void main(String[] args) {
Map<Object, WeakReference<Object>> map = new HashMap();
Object object = new Object();
WeakReference<Object> oReference = new WeakReference<Object>(object);
map.put( "Test", oReference);
object = null;
System.gc();
//map依然可以获取弱引用但是对象已经被回收了
System.out.println(map.get("Test").get());
}
}
当然这只是简单的阐释了软引用的缓存机制,可以发现这种缓存方式仍然有不好的地方那就是map的关联关系还没有被消除掉,在下一篇我就会写一个更详细的缓存机制