如果JVM垃圾回收器 GC 可达性分析结果为可达,表示引用类型仍然被引用着,这类对象始终不会被垃圾回收器回收,即使JVM发生OOM也不会回收。而如果 GC 的可达性分析结果为不可达,那么在GC时会被回收。
String strong = "strongReference";
软引用是一种比强引用生命周期稍弱的一种引用类型。在JVM内存充足的情况下,软引用并不会被垃圾回收器回收,只有在JVM内存不足的情况下,才会被垃圾回收器回收。
public void testSoftReference(){ // 分配 10 M 内存给 soft SoftReference<byte[]> soft = new SoftReference<>(new byte[1024*1024*10]); // 查看 soft 对象 System.out.println("new: "+soft.get()); // 当内存充足时,建议进行一次gc System.gc(); // 查看gc后 soft对象 System.out.println("第一次gc: "+soft.get()); // 在此申请一个强引用类型,使得分配的内存之和大于jvm最大可使用内存 byte[] strong = new byte[1024*1024*10]; // 查看soft对象 System.out.println("内存不足: "+soft.get()); }
在执行代码前,通过-xms15m设置最大堆内存为15M,这是关键,如果不设置这步,会无法复现下方结果。
打印结果为:
通过示例可以看到当第一次gc之后,因为堆内存足够,因此soft对象没有被回收,但是当内存不足时,soft的对象就被回收了。
所以软引用一般用来实现一些内存敏感的缓存,只要内存空间足够,对象就会保持不被回收掉。例如在处理图像时对大的图像文件进行缓存。
弱引用是一种比软引用生命周期更短的引用。它的生命周期很短,不论当前内存是否充足,都只能存活到下一次垃圾收集之前。
此外,弱引用可以和一个引用队列联合使用,如果弱引用引用的对象被垃圾回收,JVM会把这个弱引用加入到与之关联的引用队列中。
弱引用一个典型的用法就是在ThreadLocal中的使用。具体示例可以查看:
ThreadLocal 简介
与弱引用相比,虚引用是比弱引用更弱的引用,甚至无法通过虚引用无法通过get方法来获得指向的值,源码中直接返回了null
;
/** * Returns this reference object's referent. Because the referent of a * phantom reference is always inaccessible, this method always returns * <code>null</code>. * * @return <code>null</code> */ public T get() { return null; }
相比于弱引用,虚引用在使用时必须和一个ReferenceQueue一起使用,当虚引用指向的对象被GC收集时,这个对象并不会被马上释放,此时PhantomReference会被放入到关联的ReferenceQueue中去,只有当处理了对应队列中的虚引用之后,对象占用的空间才会被正式释放。
注意: 在Java 8以及之前的版本中,在虚引用回收后,虚引用指向的对象才会回收。在Java 9以及更新的版本中,虚引用不会对对象的生存产生任何影响。
public class TestReference { private static final List<Object> list = new ArrayList<>(); private static final ReferenceQueue<M> RQ = new ReferenceQueue(); static class M{ @Override protected void finalize() throws Throwable { System.out.println("我被回收了"); } } public void testPhantomReference(){ PhantomReference<M> phantomReference = new PhantomReference(new M() ,RQ); System.out.println("获取虚引用对象:"+ phantomReference.get()); new Thread(()->{ while(true){ list.add(new byte[1024*1024*2]); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(() -> { while (true) { Reference poll = RQ.poll(); if (poll != null) { System.out.println("虚引用被回收了:" + poll); } } }).start(); // 阻塞进程 Scanner scanner = new Scanner(System.in); scanner.hasNext(); } }
执行这端代码:
此时,虚函数指向的对象M的finalize方法已经被执行了,即对象已经被回收了,同时在队列中也取到了poll函数。
另外还可以通过visualVM来查看对应的JVM内存占用情况:
static class M{ byte[] value = new byte[1024*1024*6]; @Override protected void finalize() throws Throwable { System.out.println("我被回收了"); } } public void testPhantomReference2(){ PhantomReference<M> phantomReference = new PhantomReference(new M() ,RQ); System.gc(); // phantomReferenc // Reference poll // poll = null; System.gc(); byte[] b = new byte[1024*1024*8]; Scanner scanner = new Scanner(System.in); scanner.hasNext(); }
当执行这段这段代码时,即使执行了System.gc(), 虚引用指向的老年代中保存这M中声明的6M的变量还是存在,当新申明8M的空间时,由于空间不够,因此直接OOM报错。
在注释存在的情况下,进行gc:
去掉注释,显式置空虚引用,继续gc