1. 程式人生 > >記憶體洩漏分析總結

記憶體洩漏分析總結

記憶體洩露原因分析

在JAVA中JVM的棧記錄了方法的呼叫,每個執行緒擁有一個棧。線上程的執行過程當中,執行到一個新的方法呼叫,就在棧中增加一個記憶體單元,即幀(frame)。在frame中,儲存有該方法呼叫的引數、區域性變數和返回地址。然而JAVA中的區域性變數只能是基本型別變數(int),或者物件的引用。所以在棧中只存放基本型別變數和物件的引用。引用的物件儲存在堆中。 當某方法執行結束時,該方法對應的frame將會從棧中刪除,frame中所有區域性變數和引數所佔有的空間也隨之釋放。執行緒回到原方法繼續執行,當所有的棧都清空的時候,程式也就隨之執行結束。 而對於堆記憶體,堆存放著普通變數。在JAVA中堆記憶體不會隨著方法的結束而清空
,所以在方法中定義了局部變數,在方法結束後變數依然存活在堆中。 綜上所述,棧(stack)可以自行清除不用的記憶體空間。但是如果我們不停的建立新物件,堆(heap)的記憶體空間就會被消耗盡。所以JAVA引入了垃圾回收(garbage collection,簡稱GC)去處理堆記憶體的回收,但如果物件一直被引用無法被回收,造成記憶體的浪費,無法再被使用。所以物件無法被GC回收就是造成記憶體洩露的原因!

垃圾回收機制

垃圾回收(garbage collection,簡稱GC)可以自動清空堆中不再使用的物件。在JAVA中物件是通過引用使用的。如果再沒有引用指向該物件,那麼該物件就無從處理或呼叫該物件,這樣的物件稱為不可到達
(unreachable)。垃圾回收用於釋放不可到達的物件所佔據的記憶體。 實現思想:我們將棧定義為root,遍歷棧中所有的物件的引用,再遍歷一遍堆中的物件。因為棧中的物件的引用執行完畢就刪除,所以我們就可以通過棧中的物件的引用,查詢到堆中沒有被指向的物件,這些物件即為不可到達物件,對其進行垃圾回收。
垃圾回收實現思想 如果持有物件的強引用,垃圾回收器是無法在記憶體中回收這個物件。

引用型別

在JDK 1.2以前的版本中,若一個物件不被任何變數引用,那麼程式就無法再使用這個物件。也就是說,只有物件處於可觸及(reachable)狀態,程式才能使用它。從JDK 1.2版本開始,把物件的引用分為4種級別,從而使程式能更加靈活地控制物件的生命週期。這4種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。 1. 強引用(Strong reference)
實際編碼中最常見的一種引用型別。常見形式如:A a = new A();等。強引用本身儲存在棧記憶體中,其儲存指向對記憶體中物件的地址。一般情況下,當對記憶體中的物件不再有任何強引用指向它時,垃圾回收機器開始考慮可能要對此記憶體進行的垃圾回收。如當進行編碼:a = null,此時,剛剛在堆中分配地址並新建的a物件沒有其他的任何引用,當系統進行垃圾回收時,堆記憶體將被垃圾回收。 2. 軟引用(Soft Reference) 軟引用的一般使用形式如下:
A a = new A(); SoftReference<A> srA = new SoftReference<A>(a);
軟引用所指示的物件進行垃圾回收需要滿足如下兩個條件: 1.當其指示的物件沒有任何強引用物件指向它; 2.當虛擬機器記憶體不足時。 因此,SoftReference變相的延長了其指示物件佔據堆記憶體的時間,直到虛擬機器記憶體不足時垃圾回收器才回收此堆記憶體空間。 3. 弱引用(Weak Reference) 同樣的,軟引用的一般使用形式如下:
A a = new A(); WeakReference<A> wrA = new WeakReference<A>(a);
WeakReference不改變原有強引用物件的垃圾回收時機,一旦其指示物件沒有任何強引用物件時,此物件即進入正常的垃圾回收流程。 4. 虛引用(Phantom Reference) 記憶體洩漏的8種原因
  • 全域性程序(process-global)的static變數。這個無視應用的狀態,持有Activity的強引用的怪物。
  • 活在Activity生命週期之外的執行緒。沒有清空對Activity的強引用。
1.Static Activities 在類中定義了靜態Activity變數,把當前執行的Activity例項賦值於這個靜態變數。 如果這個靜態變數在Activity生命週期結束後沒有清空,就導致記憶體洩漏。因為static變數是貫穿這個應用的生命週期的,所以被洩漏的Activity就會一直存在於應用的程序中,不會被垃圾回收器回收。 2.Static Views 3.Inner class 建立一個內部類,持有一個靜態變數的引用 4.Anonymous class 當非同步任務在後臺執行耗時任務期間,Activity不幸被銷燬了(譯者注:使用者退出,系統回收),這個被AsyncTask持有的Activity例項就不會被垃圾回收器回收,直到非同步任務結束。 5.Handler 同樣道理,定義匿名的Runnable,用匿名類Handler執行Runnable內部類會持有外部類的隱式引用,被傳遞到Handler的訊息佇列MessageQueue中,在Message訊息沒有被處理之前,Activity例項不會被銷燬了,於是導致記憶體洩漏。 6.Thread 7.TimeTask 只要是匿名類的例項,不管是不是在工作執行緒,都會持有Activity的引用,導致記憶體洩漏。 8.System service 通過Context.getSystemService(int name)可以獲取系統服務。這些服務工作在各自的程序中,幫助應用處理後臺任務,處理硬體互動。如果需要使用這些服務,可以註冊監聽器,這會導致服務持有了Context的引用,如果在Activity銷燬的時候沒有登出這些監聽器,會導致記憶體洩漏。 總結 看過那麼多會導致記憶體洩漏的例子,容易導致吃光手機的記憶體使垃圾回收處理更為頻發,甚至最壞的情況會導致OOM。垃圾回收的操作是很昂貴的開銷,會導致肉眼可見的卡頓。所以,例項化的時候注意持有的引用鏈,並經常進行記憶體洩漏檢查。