强软弱虚四种引用和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

 

posted @ 2020-04-20 19:01  palapala  阅读(582)  评论(0编辑  收藏  举报