java - 內存泄漏

內存泄漏問題產生緣由java

  長生命週期的對象持有短生命週期對象的引用就極可能發生內存泄露,儘管短生命週期對象已經再也不須要,可是由於長生命週期對象持有它的引用而致使不能被回收,這就是java中內存泄露的發生場景,通俗地說,就是程序員可能建立了一個對象,之後一直再也不使用這個對象,這個對象卻一直被引用,即這個對象無用可是卻沒法被垃圾回收器回收的,這就是java中可能出現內存泄露的狀況程序員

        Vector v = new Vector(10);
        for (int i = 1; i < 100; i++) {
            Object o = new Object();
            v.add(o);
            o = null;
        }

  在這個例子中,循環申請Object 對象,並將所申請的對象放入一個Vector 中,若是僅僅釋放引用自己(o=null),那麼Vector 仍然引用該對象,因此這個對象對GC 來講是不可回收的。所以,若是對象加入到Vector 後,還必須從Vector 中刪除,最簡單的方法就是將Vector對象設置爲null。數組

 

ThreadLocalide

1. ThreadLocal 中維護了一個內部類ThreadLocalMap<ThreadLocal, Object>,因此必需要有ThreadLocal才能操做ThreadLocalMap變量函數

2. Thread類中持有一個ThreadLocalMap 的引用。每一個線程中能夠由多個ThreadLocal 變量this

  每一個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key爲一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每一個key都弱引用指向threadlocal. 當把threadlocal實例置爲null之後,沒有任何強引用指向threadlocal實例,因此threadlocal將會被gc回收. 可是,咱們的value卻不能回收,由於存在一條從current thread鏈接過來的強引用. 只有當前thread結束之後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將所有被GC回收. 
  因此得出一個結論就是隻要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設爲null和線程結束這段時間不會被回收的,就發生了咱們認爲的內存泄露。其實這是一個對概念理解的不一致,也沒什麼好爭論的。最要命的是線程對象不被回收的狀況,這就發生了真正意義上的內存泄露。好比使用線程池的時候,線程結束是不會銷燬的,會再次使用的。就可能出現內存泄露。

 

開5個線程,每個線程中放入ThreadLocal的變量,初始值爲0。每一個線程都單獨操做此變量,線程之間沒有影響spa

public class ThreadLocalTest {  
        //建立一個Integer型的線程本地變量  
        public static final ThreadLocal<integer> local = new ThreadLocal<integer>() {  
            @Override 
            protected Integer initialValue() {  
                return 0;  
            }  
        };  
        //計數  
        static class Counter implements Runnable{  
            @Override 
            public void run() {  
                //獲取當前線程的本地變量,而後累加100次  
                int num = local.get();  
                for (int i = 0; i < 100; i++) {  
                    num++;  
                }  
                //從新設置累加後的本地變量  
                local.set(num);  
                System.out.println(Thread.currentThread().getName() + " : "+ local.get());  
            }  
        }  
        public static void main(String[] args) throws InterruptedException {  
            Thread[] threads = new Thread[5];  
            for (int i = 0; i < 5; i++) {          
                threads[i] = new Thread(new Counter() ,"CounterThread-[" + i+"]");  
                threads[i].start();  
            }   
        }  
    } 

輸出:

CounterThread-[2] : 100
CounterThread-[0] : 100
CounterThread-[3] : 100
CounterThread-[1] : 100
CounterThread-[4] : 100線程

 

對initialValue函數的正確理解code

  

public class ThreadLocalMisunderstand {  
   
    static class Index {  
        private int num;   
        public void increase() {  
            num++;  
        }  
        public int getValue() {  
            return num;  
        }  
    }  
    private static Index num=new Index();  
    //建立一個Index型的線程本地變量  
    public static final ThreadLocal<index> local = new ThreadLocal<index>() {  
        @Override 
        protected Index initialValue() {  
            return num;  
        }  
    };  
    //計數  
    static class Counter implements Runnable{  
        @Override 
        public void run() {  
            //獲取當前線程的本地變量,而後累加10000次  
            Index num = local.get();  
            for (int i = 0; i < 10000; i++) {  
                num.increase();  
            }  
            //從新設置累加後的本地變量  
            local.set(num);  
            System.out.println(Thread.currentThread().getName() + " : "+ local.get().getValue());  
        }  
    }  
    public static void main(String[] args) throws InterruptedException {  
        Thread[] threads = new Thread[5];  
        for (int i = 0; i < 5; i++) {          
            threads[i] = new Thread(new Counter() ,"CounterThread-[" + i+"]");  
        }   
        for (int i = 0; i < 5; i++) {      
            threads[i].start();  
        }  
    }  
}

輸出:
CounterThread-[0] : 12019
CounterThread-[2] : 14548
CounterThread-[1] : 13271
CounterThread-[3] : 34069
CounterThread-[4] : 34069

  如今獲得的計數不同了,而且每次運行的結果也不同,說好的線程本地變量呢?對象

  以前提到,咱們經過覆蓋initialValue函數來給咱們的ThreadLocal提供初始值,每一個線程都會獲取這個初始值的一個 副本。而如今咱們的初始值是一個定義好的一個對象,num是這個對象的引用。換句話說咱們的初始值是一個引用。引用的副本和引用指向的不就是同一個對象嗎?
 
  若是咱們想給每個線程都保存一個Index對象應該怎麼辦呢?那就是建立對象的副本而不是對象引用的副本。
private static ThreadLocal<index> local = new ThreadLocal<index>() {  
    @Override 
    protected Index initialValue() {  
        return new Index(); //注意這裏,新建一個對象  
    }  
}

 

 

ThreadLocal源碼分析

存儲結構

public class ThreadLocal<t> {
......
    static class ThreadLocalMap {//靜態內部類
        static class Entry extends WeakReference<threadlocal> {//鍵值對
            //Entry是ThreadLocal對象的弱引用,this做爲鍵(key)
            /** The value associated with this ThreadLocal. */
            Object value;//ThreadLocal關聯的對象,做爲值(value),也就是所謂的線程本地變量
 
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        ......
        private Entry[] table;//用數組保存全部Entry,採用線性探測避免衝突
    }
......
}

 

內存泄露與WeakReference

static class Entry extends WeakReference<threadlocal> {
    /** The value associated with this ThreadLocal. */
    Object value;
 
    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

  一旦threadLocal的強引用斷開,key的內存就能夠獲得釋放。只有當線程結束後,value的內存才釋放。

  每一個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap。Map中的key爲一個threadlocal實例。這個Map的確使用了弱引用,不過弱引用只是針對key。每一個key都弱引用指向threadlocal。當把threadlocal實例置爲null之後,沒有任何強引用指threadlocal實例,因此threadlocal將會被gc回收。可是,咱們的value卻不能回收,由於存在一條從current thread鏈接過來的強引用。

  只有當前thread結束之後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將所有被GC回收.

  因此得出一個結論就是隻要這個線程對象被gc回收,就不會出現內存泄露。可是value在threadLocal設爲null線程結束這段時間不會被回收,就發生了咱們認爲的「內存泄露」。

  所以,最要命的是線程對象不被回收的狀況,這就發生了真正意義上的內存泄露。好比使用線程池的時候,線程結束是不會銷燬的,會再次使用的,就可能出現內存泄露。  
  爲了最小化內存泄露的可能性和影響,在ThreadLocal的get,set的時候,遇到key爲null的entry就會清除對應的value。

  因此最怕的狀況就是,threadLocal對象設null了,開始發生「內存泄露」,而後使用線程池,這個線程結束,線程放回線程池中不銷燬,這個線程一直不被使用,或者分配使用了又再也不調用get,set方法,或者get,set方法調用時依然沒有遇到key爲null的entry,那麼這個期間就會發生真正的內存泄露。

  使用ThreadLocal須要注意,每次執行完畢後,要使用remove()方法來清空對象,不然 ThreadLocal 存放大對象後,可能會OMM。

相關文章
相關標籤/搜索