Java四种引用类型与ThreadLocal内存泄露

java中引用类型

  1. 强引用 NormalReference(一个普通变量指向一个对象,引用消失以后,对象就会被GC)
    Object o = new Object()
  2. 软引用 SoftReference(有一个软引用对象,软引用对象中有个引用指向一个对象,这个对象是被软引用连着的,在GC的时候会被特殊处理,堆内存不够用的时候就会被回收)
/**
 * -Xmx20M 设置最大堆内存为20MN
 */
public class SoftReferenceDemo {
    public static void main(String[] args) {
        SoftReference m = new SoftReference(new byte[1024*10245*10]);
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(500);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(m.get());

        //再分配一个数组,堆内存装不下,这时候系统会进行GC
        byte[] b = new byte[1024*1024*15];
        System.out.println(m.get());
    }
}
  1. 弱引用 WeakReference(有一个弱引用对象,弱引用对象中有个引用指向一个对象,这个对象只要遇到GC就会被回收)弱引用作用是解决内存泄露
public class WeakReferenceDemo {
    public static void main(String[] args) {
        WeakReference m=new WeakReference(new Person());

        System.out.println(m.get());
        System.gc();
        System.out.println(m.get());
    }
}
  1. 虚引用 PhantomReference(有跟没有差不多,永远get不到,作用是管理堆外内存)

ThreadLocal

threadLocal就是一个容器,A线程只能拿到自己放入threadLocal的东西,拿不到B线程放进去的东西

使用案例

不使用threadLocal

public class ThreadLocalDemo {
    volatile static Person p = new Person();

    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(p.name);
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.name="lisi";
            }
        }).start();
    }
}

class Person{
    String name="zhangsan";
}

上述打印结果lisi
使用threadLocal以后

public class ThreadLocalDemo {
//    volatile static Person p = new Person();
    static ThreadLocal t1= new ThreadLocal();
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(t1.get());
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                t1.set(new Person());
            }
        }).start();
    }
}

class Person{
    String name="zhangsan";
}

打印结果为null

源码解析

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
   ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

获取的是当前线程里面的threadlocalmap,自然B线程无法获取A线程放置的内容,threadLocal最明显的使用时spring中的事务@Transaction

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();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
// new一个虚引用
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
// 这个Entry是一个weakReference
static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
// 下面这句等效于 new WeakReference(new K()),即k对象是被弱引用指向的
                super(k);
                value = v;
            }
        }

流程:new 一个threadLocal,然后调用set方法,引用了当前线程的threadLocalMap,然后创建一个Entry对象即弱引用对象,让该弱引用对象指向new的threadLocal对象这个key,然后就将这个Entry放到threadLocalMap中

面试题1 为什么Entry要用弱引用

ThreadLocal t1=new ThreadLocal<>();
r1.set(new Person());
t1.remove();

假设Entry为强引用,因为是强引用,当我们写t1=null的时候(或者main方法退出),t1不再使用的时候,这个new出来的threadLocal应该被回收掉,可是因为在t1中set了一个new Person(),则ThreadLocalMap中仍然有个entry的key指向这个ThreadLocal对象t1,因此该对象无法回收,如果程序一直运行,则该对象永远无法回收,因为有个强引用永远指向他,造成了内存泄露问题

因此Entry为弱引用,ThreadLocalMap的key弱指向threadLocal对象t1,只要有GC,这个t1就会被回收

Java四种引用类型与ThreadLocal内存泄露_第1张图片
image.png

当我们通过弱引用将ThreadLocal对象t1回收以后,就出现了key为null,但是value存在的情况,value则面临无法回收的局面,因为已经无法通过这个null找到这个value,导致越来越多的这种积累,造成内存泄漏

因此正常的threadLocal使用方法是确定new出来的Person不再引用以后,使用t1.remove()将整个entry行从ThreadLocalMap中删除

你可能感兴趣的:(Java四种引用类型与ThreadLocal内存泄露)