最常见的引用类型,即使内存不够也不会回收。
思考:obj1这个强引用自身是否也是对象?占用内存吗?
public class StrongReferenceDemo { public static void main(String[] args) { //obj1是一个强引用,指向new出来的实际对象 Object obj1 = new Object(); Object obj2 = obj1; obj1 = null; System.gc(); System.out.println(obj2); } }
输出结果:java.lang.Object@4d7e1886
内存不够了就回收仅有软引用指向的对象,内存够的时候不用管。
package com.shin.demo05; import java.lang.ref.SoftReference; public class SoftReferenceDemo { public static void memoryEnough(){ Object obj1 = new Object(); //创建一个软引用,指向强引用obj所指的对象。 SoftReference<Object> softReference = new SoftReference<Object>(obj1); System.out.println(obj1); System.out.println(softReference.get()); obj1 = null; System.gc(); System.out.println(obj1); System.out.println(softReference.get()); } //-Xms5m -Xmx5m 故意制造OOM public static void memoryNotEnough(){ Object obj1 = new Object(); SoftReference<Object> softReference = new SoftReference<Object>(obj1); System.out.println(obj1); System.out.println(softReference.get()); //此时只剩软引用 obj1 = null; try{ byte[] bytes = new byte[300*1024*1024]; }catch (Throwable e){ e.printStackTrace(); }finally { System.out.println(obj1); System.out.println(softReference.get()); } } public static void main(String[] args) { memoryEnough(); System.out.println("========================="); memoryNotEnough(); } }
故意制造OOM,JVM参数-Xms5m -Xmx5m。自动GC的时候把软引用指向的对象回收了。使用softReference.get()可以获得指向的对象。
输出结果:
java.lang.Object@4d7e1886 java.lang.Object@4d7e1886 null java.lang.Object@4d7e1886 ============= java.lang.Object@3cd1a2f1 java.lang.Object@3cd1a2f1 java.lang.OutOfMemoryError: Java heap space at com.shin.demo05.SoftReferenceDemo.memoryNotEnough(SoftReferenceDemo.java:25) at com.shin.demo05.SoftReferenceDemo.main(SoftReferenceDemo.java:36) null null
仅被弱引用指向的对象在GC的时候一定会被回收,无论内存是否足够。
import java.lang.ref.WeakReference; public class WeakReferenceDemo { public static void memoryEnough(){ Object obj1 = new Object(); WeakReference<Object> weakReference = new WeakReference<Object>(obj1); System.out.println(obj1); System.out.println(weakReference.get()); obj1 = null; System.gc(); System.out.println(obj1); System.out.println(weakReference); System.out.println(weakReference.get()); } public static void main(String[] args) { memoryEnough(); } }
同样是使用weakReference.get()获取弱引用指向的对象。weakReference本身也要占用内存,可以理解为引用类型的对象。
输出结果:
java.lang.Object@4d7e1886 java.lang.Object@4d7e1886 null java.lang.ref.WeakReference@3cd1a2f1 null
仅被虚引用指向的对象也是只要GC就会回收,此外不可以通过虚引用get到指向的对象。
但是可以将虚引用回收的时候入引用队列ReferenceQueue。
import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class PhantomReferenceDemo { public static void main(String[] args) { Object obj1 = new Object(); ReferenceQueue queue = new ReferenceQueue(); PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj1,queue); System.out.println(obj1); System.out.println(phantomReference.get()); System.out.println(queue.poll()); System.out.println("==========================="); obj1 = null; System.gc(); System.out.println(obj1); System.out.println(phantomReference.get()); System.out.println(queue.poll()); } }
输出结果:
java.lang.Object@4d7e1886 null null =========================== null null java.lang.ref.PhantomReference@3cd1a2f1
软弱虚引用都可以和引用队列配合,在被回收的时候进入引用队列。注意入队的是引用,不是引用指向的对象。
import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; public class ReferenceQueueDemo { public static void main(String[] args) { Object obj1 = new Object(); ReferenceQueue queue = new ReferenceQueue(); WeakReference<Object> weakReference = new WeakReference<Object>(obj1,queue); System.out.println(obj1); System.out.println(weakReference.get()); System.out.println(queue.poll()); System.out.println("==========================="); //只剩下weak-reference,实际对象会被GC回收 obj1 = null; System.gc(); System.out.println(obj1); System.out.println(weakReference.get()); //回收之后入队 System.out.println(queue.poll()); } }
输出结果:
java.lang.Object@4d7e1886 java.lang.Object@4d7e1886 null =========================== null null java.lang.ref.WeakReference@3cd1a2f1
WeakHashMap实现原理和HashMap差不多,区别在于WeakHashMap的key是一个弱引用。
public class WeakHashMapDemo { public static void notWeak(){ HashMap<Integer, String> map = new HashMap<>(); Integer key = new Integer(1); String value = "HashMap"; map.put(key,value); System.out.println(map); key = null; //显然还在,想要删除的话需要使用remove()方法 System.out.println(map); System.gc(); //显然还在,HashMap内部都是强引用 System.out.println(map); } public static void weak(){ WeakHashMap<Integer, String> map = new WeakHashMap<>(); Integer key = new Integer(1); String value = "WeakHashMap"; map.put(key,value); System.out.println(map); key = null; //不GC的话还在 System.out.println(map); System.gc(); //WeakHashMap的key是一个弱引用,GC之后key原来指向的对象被回收了 //但是这里看到的是整个键值对都没了,其实用到了引用队列 System.out.println(map); } public static void main(String[] args) { notWeak(); weak(); }
输出结果:
{1=HashMap} {1=HashMap} {1=HashMap} {1=WeakHashMap} {1=WeakHashMap} {}
下面一段话摘自《Java核心技术卷1》:
设计 WeakHashMap类是为了解决一个有趣的问题。如果有一个值,对应的键已经不再使用了,将会出现什么情况呢? 假定对某个键的最后一次引用已经消亡,不再有任何途径引用这个值的对象了。但是,由于在程序中的任何部分没有再出现这个键,所以,这个键值对无法从映射中删除。为什么垃圾回收器不能够删除它呢? 难道删除无用的对象不是垃圾回收器的工作吗?
遗憾的是,事情没有这样简单。垃圾回收器跟踪活动的对象。只要映射对象是active的,其中的所有桶也是active的,它们不能被回收。因此,需要由程序负责从长期存活的映射表中删除那些无用的值。 或者使用 WeakHashMap完成这件事情。当对键的唯一引用来自散列条目时, 这一数据结构将与垃圾回收器协同工作一起删除键 / 值对。
下面是这种机制的内部运行情况。WeakHashMap使用弱引用(weak references) 保存键。WeakReference对象将引用保存到另外一个对象中,在这里,就是散列键。对于这种类型的对象,垃圾回收器用一种特有的方式进行处理。通常,如果垃圾回收器发现某个特定的对象已经没有他人引用了,就将其回收。然而,如果某个对象只能由 WeakReference引用,垃圾回收器仍然回收它,但要将引用这个对象的弱引用放人队列中。WeakHashMap将周期性地检查队列,以便找出新添加的弱引用。一个弱引用进人队列意味着这个键不再被他人使用,并且已经被收集起来。于是, WeakHashMap将删除对应的条目。
具体看WeakHashMap源码中的expungeStaleEntries()方法。
如果一个类继承了WeakReference会怎样?
import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; //MyEntry继承了WeakReference,那么MyEntry本身就是一个弱引用 //通过父类的构造方法可以指定referent和queue。 public class MyEntry extends WeakReference { //MyEntry是一个弱引用,自身还携带了一个属性value private Object value; //调用这个构造函数会返回一个MyEntry类型的弱引用 //指向的是key所指的实际对象,引用队列是queue public MyEntry(Object key, Object value, ReferenceQueue queue) { //调用父类构造函数 super(key,queue); this.value = value; } public static void main(String[] args) { //1、key是一个强引用 Object key = new Object(); Object value = new Object(); ReferenceQueue<Object> queue = new ReferenceQueue<>(); //2、创建了弱引用myEntry,指向强引用key所指的对象 // 当只剩myEntry的时候,GC回收指向的实际对象,然后把myEntry放进queue中 // 注意这里是把谁入队。引用队列入队的一定是引用,而不是引用指向的实际对象。 MyEntry myEntry = new MyEntry(key, value, queue); System.out.println("key: "+key); System.out.println("value: "+value); System.out.println("myEntry指向: "+myEntry.get()); System.out.println("queue: "+queue.poll()); System.out.println("==============================="); key = null; System.gc(); System.out.println("key: "+key); System.out.println("value: "+value); System.out.println("myEntry指向: "+myEntry.get()); System.out.println("queue: "+queue.poll()); } }
输出结果:
key: java.lang.Object@4d7e1886 value: java.lang.Object@3cd1a2f1 myEntry指向: java.lang.Object@4d7e1886 queue: null =============================== key: null value: java.lang.Object@3cd1a2f1 myEntry指向: null queue: com.shin.demo05.MyEntry@2f0e140b
可以看到myEntry指向的是原本key所指的对象,GC之后被回收了,并且myEntry进入引用队列。