強引用、軟引用、弱引用和ThreadLocal詳解

強引用

在Java中,最多的就是強引用,垃圾回收時寧願內存不足也不會回收掉強引用的對象,一般是通過 new 方式創建的強引用。

注意這裏指的垃圾回收是指在方法即在棧幀運行期間時發生的垃圾回收,此時強引用不會被回收,如果是方法運行結束即棧幀結束,此時這個方法的不管什麼對象都一定要被回收的!

強引用存在的問題:

當內存不足時,由於不會回收強引用的對象,此時如果對於程序來說對象不被使用,此時強引用對象就會佔相當一大部分堆的空間。

解決方法:

將強引用對象置爲 null ,脫離強引用即可,ArrayList的clear() 方法的原理就是將元素置爲null來做清除工作。

軟引用

軟引用用來描述一些有用但不是非必須的對象,用 SoftReferebce 表示,對於軟引用關聯的對象,只有內存不足時JVM纔會回收該對象,所以該引用適合作爲緩存,當內存足夠時,就可以利用緩存儲存查詢,當內存不足時,就可以將緩存刪除回收掉,這是典型的以空間換時間的。

弱引用

相對於強引用和軟引用來說,弱引用的強度就更小了,當JVM進行回收時,無論內存是否充足都會回收該引用的對象,弱引用用WeakReference表示。

例子

A a = new A();
B b = new B();

此時a和b都是強引用,JVM不會回收兩個對象。

A a = new A();
B b = new B();
a = null;
b = null;

當我們把他們都置爲null後,他們就和強引用脫離了關係,此時JVM會對a和b的堆中的空間進行垃圾回收。

A a = new A();
B b = new B(a);
a = null;

我們再構造方法裏傳入了a的實例,此時 a和b就有了關聯,當我們把a置爲null時,JVM也不會回收a,因爲根據GCROOT根算法,此時b也有與之的引用,此時a並不是一個遊離的對象,無法被回收。其解決方案仍然是將b置爲null,當b爲null時,此時的a的實例和b的實例一起不可達,所以可以被回收。

但是這樣的解決方案很粗糙,因爲當我們如果後面仍然要使用b,但是爲了釋放a的空間,把b也釋放了,這肯定是不能接受的,所以我們要尋找一個合適的解決方案,將在釋放a的同時,不影響b。

A a = new A();
WeakReference<A> er = new WealReference();// 將a構造成弱引用
B b = new B(er);



class A{
//.....
}
class B{
  A a;
  public B(WeakReference<A> er){
    this.a = er.get();//通過get方法關聯
  }
}

解決方案是將a構建成一個弱引用,我們用b依賴一個弱引用,此時我們將a置爲null,a的引用上只有弱引用,此時如果JVM發生了垃圾回收,會立即回收掉這個 只有弱引用 的對象。

所以弱引用的好處是在對象的相互依賴不是很清晰的情況下合理釋放對象,以免造成不必要的內存泄露

ThreadLocal

說到弱引用就不得不提ThreadLocal,ThreadLocal是線程私有的變量,一般使用格式爲

ThreadLocal<String> t = new ThreadLocal<>();
t.set("threadTest");
t.get();

我們可以創建很多個ThreadLocal變量,然後一一用set爲他們賦值,使用get取值。

我們看看其源碼:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

這裏面涉及到一個ThreadLocalMap,ThreadLocalMap是一個內部靜態類,爲當前線程存儲了所有的ThreadLocal,Key爲ThreadLocal本身,即上面例子中的t,而其Value爲ThreadLocal中存的值。

static class Entry extends WeakReference<ThreadLocal<?>> {
   /** The value associated with this ThreadLocal. */
      Object value;

     Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

我們觀察源碼發現,ThreadLocal中存Value的是一個Entry內部靜態類,並且這個類是繼承了WeakReference,也就是說當前的Entry是一個弱引用,那麼爲什麼要將ThreadLocal中的Entry當成弱引用呢?

如果是強引用,會發生什麼?

當我們將某個ThreadLocal變量,假如是上面例子中的t置爲null,也就是說我們用完了這個ThreadLocal,要將它處理掉時,此時我們仍然只是將其內部的Map的Key置爲了null,其Value仍然和Map有着強引用的關聯,此時就會發生內存泄露!

有的同學可能會問:線程結束後所有的ThreadLocal不都被回收了嗎?

是的,但是如果線程的生命週期很長亦或是線程池創建的線程,那麼線程幾乎不可結束,那麼這個內存泄露就永遠存在了!

所以這個弱引用的Entry是爲了解決內存泄露的!當我們把Key置爲null後,此時這個Value即Entry只有一個弱引用與之關聯,在下一次GC是一定會被收集掉。

但是由於GC不可控,如果很久來來一次GC怎麼辦?這時仍然會發生內存泄露,時間點處於置爲null之後到GC之前。

所以ThreadLocal也提供了折中的辦法,後續會將key爲null的Entry清理掉,但是爲了避免手動清除,所以儘量使用clear()方法來解決ThreadLocal而不是置爲null。

輸出不易,麻煩點個贊吧(#.#)

打個廣告:歡迎關注公衆號,這裏將更新質量高的技術文章,回覆pdf更有pdf大禮包相送!
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章