强软弱虚四种引用
1)强引用:平时new出来的对象,只要有引用在 即使发生GC也回收不了
2)软引用:空间不够就回收,软引用适合做缓存,空间足够就放在那里,不够用就回收
*** * 空间不够就会回收 * 软引用适合做缓存(空间足够就放在那里 不够用会回收) * -Xmx=20M */ public class T02_SoftReference { public static void main(String[] args) throws Exception{ /*** * m指向SoftReference 对象 * SoftReference 对象里面还有一个引用 是软引用 指向一个字节数组(10M) */ SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]); System.out.println(m.get()); System.gc(); /**gc不是在主线程 所以sleep 1s**/ Thread.sleep(1000); System.out.println(m.get()); //在new一个数组(强引用) heap将装不下,这时候系统会垃圾回收一次 如果不够 就把软引用 byte[] b=new byte[1024*1024*15]; System.out.println(m.get()); }
软引用的内存结构(虚引用和弱引用的内存结构图也相似)
3)弱引用:遇到GC就会回收,解决ThreadLocal内存泄露的问题(参考下面ThreadLocal泄露)
当这个弱引用指向对象M的时候,还有一个强引用执向M的时候,只要强引用消失掉,这个M对象就应该被回收(不需要在额外管理那个弱引用了),这就是弱引用的用处
一般用在容器里,如:weekHashMap
4)虚引用:管理堆外内存,比如DirectByteBuffer(NIO的API 直接指向堆外内存 不需要Copy到JVM中 操作系统直接管理,JVM回收不了 0拷贝)
即:jvm内部可以访问操作系统管理的内存(用户空间可以管理内核空间的内存)
当某个引用被回收的时候 会通知队列(把信息填到对面里面当中),然后由GC线程清理堆外内存
public class T04_PhantomReference { public static final List<Object> LIST = new ArrayList<>(); public static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>(); public static void main(String[] args) { PhantomReference<M> phantomReference=new PhantomReference<>(new M(),QUEUE); new Thread(() -> { while (true){ LIST.add(new byte[1204*1024*10]); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } /**虚引用永远拿不到值*/ System.out.println(phantomReference.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(); } } }
ThreadLocal内存泄露
1) ThreadLocal内部使用虚引用防止内存泄露:我们看ThreadLocal的set方法
ThreadLocal的set方法 后面的实现就是往Entry数组中 通过Entry的构造方法增加一个对象,
其中Entry构造里面调用了super(k),相当于用一个弱引用指向key。也就是指向ThreadLocal的引用是弱引用
Entry里面这个key是被一个弱引用指向的ThreadLocal对象
ThreadLocal<M> t = new ThreadLocal<>(); t.set(new M());
ThreadLocal的set方法 后面的实现就是往Entry数组中 通过Entry的构造方法增加一个对象,
如果我们用强引用的话:方法结束 ThreadLocal应该要回收掉
但是往线程t里面set的一个对象M,即使t为null了,Entry里面 依然会有一个强引用指向ThreadLocal(ThreadLocal对象既被t引用指向, 还被一个弱引用指向)
这个时候 因为Entry中的强引用会可能产生内存泄露(这个需要结合ThreadLocal的源码理解)
除非线程结束,但是有些线程是7X24小时不断开着的(比如NIO的Selector轮训)
2)ThreadLocal用完remove()防止内存泄露
因为源代码提示使用static关键字修饰ThreadLocal,
这时候:如果不进行remove()操作(remove就是把整个Entry从ThreadLocalMap里面删除) 上面t所持有的ThreadLocal对象也不会被释放的
一直set() 而不remove()也会引起内存泄露
所以:如果ThreadLocal被static修饰时,如果忘记remove() 也可能引起内存泄露