强引用(NormalReference) 我们平时用的基本上都是强引用M m = new M();
只要强引用在,gc不回收该对象
1 2 3 4 5 6 7 8 9 public class M { @Override protected void finalize () throws Throwable { System.out.println("finalize方法被调用" ); super .finalize(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class 强引用 { public static void main (String[] args) throws IOException { M m = new M(); System.gc(); System.out.println(m); m = null ; System.gc(); System.out.println(m); System.in.read(); } }
打印结果如下
引用.M@14ae5a5 finalize方法被调用 null
软引用(SoftReference) 软引用对象会在堆内存不足时回收
适合做缓存
在开始实验之前,先将右上角run按钮左边的本类名的v按钮选择Edit Configurations在vm options添加-Xmx20M,表示将堆最大值设置为20M
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class 软引用 { public static void main (String[] args) { SoftReference<byte []> m = new SoftReference<>(new byte [1024 * 1024 * 10 ]); System.out.println(m.get()); System.gc(); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(m.get()); byte [] b = new byte [1024 * 1024 * 11 ]; System.out.println(m.get()); } }
打印结果为
[B@14ae5a5 [B@14ae5a5 null
如果b的内存太大,堆内存装不下,那么会报OutOfMemoryError: Java heap space异常
弱引用(WeakReference) 弱引用是遇到gc就回收
1 2 3 4 5 6 7 8 9 10 public class 弱引用 { public static void main (String[] args) { WeakReference<M> m = new WeakReference<>(new M()); System.out.println(m.get()); System.gc(); System.out.println(m.get()); } }
ThreadLocal中的Entry 弱引用很经典的一个例子就是ThreadLocal中的Entry,在之前的博客 中有分析ThreadLocal为什么是弱引用,这里再次复习一下
当我们ThreadLocal tl = new ThreadLocal();的时候,实际上有两个引用指向了ThreadLocal,一个是tl本身是一个强引用,一个是getMap(当前线程)得到的ThreadLocalMap,它里面的Entry的key是一个弱引用,指向了ThreadLocal。
为什么这里要使用弱引用呢?
如果是强引用,则tl和key都是强引用,我们一般都是操作tl,也就是tl = null,但是key依然存在强引用,于是导致无法被gc回收,存在内存泄漏问题。
使用弱引用就不会内存泄漏?
不是的,即使key是弱引用,但是如果key被回收变为null,value无法被访问,依然存在内存泄漏问题。解决的办法是主动调用tl.remove();
虚引用(PhantomReference) 一般没人用,只有写类库的人会用
无论是否被回收,都get不到值
1 2 3 4 5 6 7 8 public class 虚引用 { private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>(); public static void main (String[] args) { PhantomReference<M> m = new PhantomReference<>(new M(), QUEUE); System.out.println(m.get()); } }
作用是回收前有个通知,相当于一个钩子函数,堆内存不足的时候并没有直接回收,而是放进QUEUE回收队列里,gc会不断判断队列里是否有值,有则poll,这就相当于一个通知,gc如果想,可以对poll出来的这个虚引用做一些特殊处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class 虚引用 { private static final List<Object> LIST = new LinkedList<>(); private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>(); public static void main (String[] args) { PhantomReference<M> m = new PhantomReference<>(new M(), QUEUE); System.out.println(m.get()); new Thread(()->{ while (true ){ try { LIST.add(new byte [1024 * 1024 * 2 ]); Thread.sleep(500 ); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } System.out.println(m.get()); } }).start(); new Thread(()->{ while (true ){ Reference<? extends M> poll = QUEUE.poll(); if (poll != null ){ System.out.println("jvm回收虚引用对象" + poll); } } }).start(); try { Thread.sleep(500 ); } catch (InterruptedException e) { e.printStackTrace(); } } }
实际应用中,虚引用常被用于管理直接内存(堆外内存)
当然,堆外内存不一定是硬件,只是举个例子,硬件通过驱动程序加载到内存中才能供操作系统调用,以前JVM无法直接控制堆外内存, 需要拷贝到JVM的堆中才能控制,这种拷贝需要系统调用,涉及用户态和内核态的转换
零拷贝技术 java的nio提出了zero-copy零拷贝技术,可以管理堆外内存
1 ByteBuffer buffer = ByteBuffer.allocateDirect(1024 );
这个allocateDirect方法源码如下,可以看到是DirectByteBuffer,这个DirectByteBuffer什么时候回收的呢?我们可以看到这个类下有个Cleaner,就这个名字也才得到是用来负责回收对象的
1 2 3 4 5 6 7 8 9 10 11 12 13 class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer { ... private final Cleaner cleaner; public Cleaner cleaner () { return cleaner; } public static ByteBuffer allocateDirect (int capacity) { return new DirectByteBuffer(capacity); } ... }
继续跟进Cleaner类
1 2 3 public class Cleaner extends PhantomReference <Object > { private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue(); ...
看源码讲究点到即止,是不是跟咱们自己写的 模拟简易垃圾回收线程 一模一样 有点相似?
也就是说,这个cleaner就是通过虚引用对堆外内存进行跟踪。
参考
bilibili马士兵说弱引用