总体结构
java提供了4中引用类型,在垃圾回收的时候,都有本身的各自特色。markdown
为何要区分这么多引用呢,其实这和Java的GC有密切关系。jvm
强引用(默认支持模式)
- 把一个对象赋给一个引用变量,这个引用变量就是一个强引用。
- 强引用是咱们最多见的普通对象引用,只要还有强引用指向一个对象,就能代表对象还活着
- 当内存不足的时候,jvm开始垃圾回收,对于强引用的对象,就算出现OOM也不会回收该对象的。
所以,强引用是形成java内存泄露的主要缘由之一。 - 对于一个普通的对象,若是没有其余的引用关系,只要超过了引用的做用域或者显示的将引用赋值为null,GC就会回收这个对象了。
案例
public static void main(String[] args) { Object obj=new Object();//这样定义就是一个强引用 Object obj2=obj;//也是一个强引用 obj=null; System.gc(); //不会被垃圾回收 System.out.println(obj2); }
软引用(SoftReference)
- 软引用是一种相对强化引用弱化了一些引用,须要使用java.lang.SoftReference类来实现。
- 对于只有软引用的对象来讲,
当系统内存充足时,不会被回收;
当系统内存不足时,会被回收;
案例
/** * jvm配置配置小的内存,故意产生大的对象,致使OOM, * 验证软引用在内存足够的先后是否被回收。 * 参数:-Xms:5M -Xmx:5M * @param args */ public static void main(String[] args) { Object obj=new Object();//这样定义就是一个强引用 //软引用须要使用java.lang.SoftReference来实现 //如今sf就是一个软引用 SoftReference sf=new SoftReference(obj); obj=null; System.out.println("内存足够软引用引用的对象"+sf.get()); try { final byte[] bytes = new byte[8 * 1024 * 1024]; } catch (Exception e) { e.printStackTrace(); }finally { System.out.println("内存不够:软引用引用的对象:"+sf.get()); } }
结果:ide
弱引用
- 弱引用须要用java.lang.WeakReference类来实现,它比软引用的生存期更短。
* 若是一个对象只是被弱引用引用者,那么只要发生GC,无论内存空间是否足够,都会回收该对象。函数
- ThreadLocal静态内部类ThreadLocalMap中的Entiry中的key就是一个虚引用;
案例
public static void main(String[] args) { Object obj=new Object(); WeakReference wrf=new WeakReference(obj); obj=null; System.out.println("未发生GC以前"+wrf.get()); System.gc(); System.out.println("内存充足,发生GC以后"+wrf.get()); }
结果:post
未发生GC以前java.lang.Object@2d363fb3
内存充足,发生GC以后null
你知道弱引用的话,能谈谈WeakHashMap吗?
WeakHashMap的键是“弱键”,也就是键的引用是一个弱引用。性能
public static void main(String[] args) { WeakHashMap<String,Integer> map=new WeakHashMap<>(); String key = new String("wekHashMap"); map.put(key,1); key=null; System.gc(); System.out.println(map); }
结果:map为空了。
理论上咱们只是把引用变量key变成null了,"wekHashMap"字符串应该被Map中key引用啊,不该该被GC回收啊,
可是由于key是弱引用,GC回收的时候就忽略了这个引用,把对象当成垃圾收回了。url
虚引用
- 虚引用须要 java. langref.PhantomReference类来实现。
- 顾名思义,就是形同虚设,与其余几种引用都不一样,虚引用并不会决定对象的生命周期。
若是一个对象仅被虛引用持有,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。 - 它不能单独使用也不能经过它访问对象,虚引用必须和引用队列( Reference queue)联合使用。
- 虚引用的主要做用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被 finalize之后,作某些事情的机制。
- PhantomReference的get方法老是返回null,所以没法访问对应的引用对象。
使用它的意义在于说明一个对象已经进入 finalization阶段,能够被回收,用来实现比 finalization机制更灵活的回收操做
换句话说,设置虚引用关联的惟一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理;
ReferenceQueue 引用队列
- 对象在被回收以前要被引用队列保存一下。GC以前对象不放在队列中,GC以后才对象放入队列中。
- 【经过开启线程监听该引用队列的变化状况】就能够在对象被回收时采起相应的动做。
因为虚引用的惟一目的就是能在这个对象被垃圾收集器回收时能收到系统通知,于是建立虚引用时必需要关联一个引用队列,而软引用和弱引用则不是必须的。
这里所谓的收到系统通知其实仍是经过开启线程监听该引用队列的变化状况来实现的。 - 这里还须要强调的是,
对于软引用和弱引用,当执行第一次垃圾回收时,就会将软引用或弱引用对象添加到其关联的引用队列中,而后其finalize函数才会被执行(若是没复写则不会被执行);
而对于虚引用,若是被引用对象没有复写finalize方法,则是在第一垃圾回收将该类销毁以后,才会将虚拟引用对象添加到引用队列,
若是被引用对象复写了finalize方法,则是当执行完第二次垃圾回收以后,才会将虚引用对象添加到其关联的引用队列
class User{ @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("我要被GC干了!"); } } public static void main(String[] args) throws Exception { User user=new User(); ReferenceQueue<User> queue=new ReferenceQueue(); PhantomReference prf=new PhantomReference(user,queue); //启动一个线程监控引用队列的变化 new Thread(()->{ for(;;){ final Reference<? extends User> u = queue.poll(); if (u!=null){ System.out.println("有对象被加入到了引用队列了!"+u); } } }).start(); user=null; //GC以前引用队列为空 System.out.println("GC以前"+queue.poll()); System.gc(); Thread.sleep(100); //GC以后引用队列才将对象放入 System.out.println("第一次GC以后"+queue.poll()); System.gc(); Thread.sleep(100); System.out.println("第二次GC以后"+queue.poll()); }
结果:
GC以前null
我要被GC干了!
第一次GC以后null
有对象被加入到了引用队列了!java.lang.ref.PhantomReference@549763fd
第二次GC以后java.lang.ref.PhantomReference@5aaa6d82
应用场景
软引用:SoftReference的应用场景
假若有一个应用须要读取大量的本地图片
每次读取图片都从硬盘读取会影响性能。
一次所有加载到内存中,又可能形成内存溢出。
此时,可使用软引用解决问题;
使用一个HashMap保存图片的路径和响应图片对象关联的软引用之间的映射关系,
内存不足时,jvm会自动回收这些缓存图片对象所占用的空间,能够避免OOM。
Map<String,SoftReference<Bigmap>> imageCache=new HashMap<String,SoftReference<Bitmap>>();