Java四種引用類型與ThreadLocal內(nèi)存泄露

一、java中四種引用類型

  1. 強引用 NormalReference(一個普通變量指向一個對象,引用消失以后,對象就會被GC)
    Object o = new Object()
  2. 軟引用 SoftReference(有一個軟引用對象,軟引用對象中有個引用指向一個對象,這個對象是被軟引用連著的,在GC的時候會被特殊處理,堆內(nèi)存不夠用的時候就會被回收)
/**
 * -Xmx30M 設(shè)置最大堆內(nèi)存為30M
 */
public class SoftReferenceDemo {
    public static void main(String[] args) {
        SoftReference<byte[]> m = new SoftReference<byte[]>(new byte[1024*1024*10]);
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(500);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(m.get());

        // 再分配一個數(shù)組,占據(jù)堆內(nèi)存空間的超過65%=10M,就會為null,小于65%比如9M則為對象地址
       // 如果直接和上面的對象占用堆內(nèi)存大小加起來大于30M則java.lang.OutOfMemoryError: Java heap space
        byte[] b = new byte[1024*1024*10];
        System.out.println(m.get());
    }
}
  1. 弱引用 WeakReference(有一個弱引用對象,弱引用對象中有個引用指向一個對象,這個對象只要遇到GC就會被回收)弱引用作用是解決內(nèi)存泄露
public class WeakReferenceDemo {
    public static void main(String[] args) {
        WeakReference<Person> m=new WeakReference<Person>(new Person());

        System.out.println(m.get());
        System.gc();
        System.out.println(m.get());
    }
}
  1. 虛引用 PhantomReference(有跟沒有差不多,永遠(yuǎn)get不到,作用是管理堆外內(nèi)存)

二、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";
}

上述打印結(jié)果lisi
使用threadLocal以后

public class ThreadLocalDemo {
//    volatile static Person p = new Person();
    static ThreadLocal<Person> t1= new ThreadLocal<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(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";
}

打印結(jié)果為null

源碼解析

java.lang.ThreadLocal #set

    public void set(T value) {
        //獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //獲取當(dāng)前線程的本地map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

java.lang.ThreadLocal.ThreadLocalMap #set

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

注意:類似于hashmap7的結(jié)構(gòu)是一個entry數(shù)組,entry是鏈表節(jié)點,下面也是new一個entry鍵值對作為threadLocalMap的結(jié)構(gòu)數(shù)組中成員

java.lang.ThreadLocal.ThreadLocalMap

static class ThreadLocalMap {
    // 這個Entry是一個weakReference
    static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
// 調(diào)用子類的構(gòu)造方法的時候,會先去實例化父類
// 下面這句等效于 new WeakReference(new K()),即k對象是被弱引用指向的
                super(k);
                value = v;
            }
        }
}

java.lang.ThreadLocal.ThreadLocalMap #getMap

   ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

獲取的是當(dāng)前線程里面的threadlocalmap,自然B線程無法獲取A線程放置的內(nèi)容,threadLocal最明顯的使用時spring中的事務(wù)@Transaction

流程:new 一個threadLocal,然后調(diào)用set方法,引用了當(dāng)前線程的threadLocalMap,然后創(chuàng)建一個Entry對象即弱引用對象,讓該弱引用對象指向new的threadLocal對象這個key,然后就將這個Entry放到threadLocalMap中

面試題1 為什么Entry要用弱引用

下面代碼中的這個threadLocal對象,有2個地方引用它

  1. 一個是強引用 ThreadLocal<Person> t1;
  2. 還有一個是線程的threadLocalMap的一個Entry鍵值對的key也指向他,并且這個指向是一個弱引用,弱指向
ThreadLocal<Person> t1=new ThreadLocal<>();
r1.set(new Person());
t1.remove();

內(nèi)存泄露場景1:
假設(shè)Entry為強引用,因為是強引用,當(dāng)我們寫t1=null的時候(或者main方法退出),t1不再使用的時候,這個new出來的threadLocal應(yīng)該被回收掉,可是因為在t1中set了一個new Person(),則ThreadLocalMap中仍然有個entry的key指向這個ThreadLocal對象t1,因此該對象無法回收,如果程序一直運行,則該對象永遠(yuǎn)無法回收,因為有個強引用永遠(yuǎn)指向他,造成了內(nèi)存泄露問題

因此Entry為弱引用,ThreadLocalMap的key弱指向threadLocal對象t1,只要有GC,這個t1就會被回收

內(nèi)存泄露場景2:
當(dāng)我們通過弱引用將ThreadLocal對象t1回收以后,就出現(xiàn)了key為null,但是value存在的情況,value則面臨無法回收的局面,因為已經(jīng)無法通過這個null找到這個value,導(dǎo)致越來越多的這種積累,造成內(nèi)存泄漏

**總結(jié):

因此正常的threadLocal使用方法是確定new出來的Person不再引用以后,使用t1.remove()將整個Entry行從ThreadLocalMap中刪除**

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容