强软弱虚四种引用和ThreadLocal内存泄露
强软弱虚四种引用
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 t1 = new ThreadLocal(); t1.set("A");---> map.set(this, value);
tl.set("A")==>是以tl为key "A"为value装到一个Thread的lMap里面--->threadLocalMap
所以执行ThreadLocal对象的有两个引用
1.t1会强引用执行ThreadLocal
2. ThreadLocalMap中的key 会弱引用指向ThreadLocal
map.set(this.value)进一步实现
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); } tab[i] = new Entry(key, value); }
Entry的父类是weekReference,所以key是弱引用指向ThreadLocal
为什么要用弱引用而不是强引用呢?(弱引用 当垃圾回收器到来的时候 如果没有强引用指向,就会被回收)
ThreadLocalMap里面可以装很多个ThreadLocal
如果我们用强引用的话:方法结束 ThreadLocal应该要回收掉
但是往线程t里面set的一个对象M,即使t为null了(调用t.remove()),Entry里面 依然会有一个强引用指向ThreadLocal(ThreadLocal对象既被t引用指向, 还被一个弱引用指向)
这个时候 因为Entry中的强引用会可能产生内存泄露(这个需要结合ThreadLocal的源码理解)
除非线程结束,但是有些线程是7X24小时不断开着的(比如NIO的Selector轮训),TheadLocalMap一直用着
2)ThreadLocal用完即时remove()防止内存泄露
因为源代码提示使用static关键字修饰ThreadLocal,
这时候:如果不进行remove()操作(remove就是把整个Entry从ThreadLocalMap里面删除) 上面value指向的对象A也不会被回收
所以:如果ThreadLocal被static修饰时,如果忘记remove() 也可能引起内存泄露
ps:即使remove了,回收了value,但也要靠弱引用的特点,回收key