Android - 內存泄漏 + 垃圾回收(GC)概念

Android內存泄露——全解析和處理辦法

 

內存泄露

說到內存泄露,就不得不提到內存溢出,這兩個比較容易混淆的概念,咱們來分析一下。html

  • 內存泄露程序在向系統申請分配內存空間後(new),在使用完畢後未釋放。結果致使一直佔據該內存單元,咱們和程序都沒法再使用該內存單元,直到程序結束,這是內存泄露。java

  • 內存溢出程序向系統申請的內存空間超出了系統能給的。好比內存只能分配一個int類型,我卻要塞給他一個long類型,系統就出現oom。又好比一車最多能坐5我的,你卻非要塞下10個,車就擠爆了。git

大量的內存泄露會致使內存溢出(oom)。github

 

 

引發內存泄漏的狀況數組

  • 對於使用了BraodcastReceiver,ContentObserver,File,遊標 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷。
  • 靜態內部類持有外部成員變量(或context):可使用弱引用或使用ApplicationContext。
  • 內部類持有外部類引用,異步任務中,持有外部成員變量。
  • 集合中沒用的對象沒有及時remove。
  • 不用的對象及時釋放,如使用完Bitmap後掉用recycle(),再賦null。
  • handler引發的內存泄漏,MessageQueue裏的消息若是在activity銷燬時沒有處理完,就會引發內存的泄漏,可使用弱引用解決。
  • 設置過的監聽不用時,及時移除。如在Destroy時及時remove。尤爲以addListener開頭的,在Destroy中都須要remove。
  • activity泄漏可使用LeakCanary。

內存

想要了解內存泄露,對內存的瞭解必不可少。
JAVA是在JVM所虛擬出的內存環境中運行的,JVM的內存可分爲三個區:堆(heap)、棧(stack)和方法區(method)。數據結構

  • 棧(stack):是簡單的數據結構,但在計算機中使用普遍。棧最顯著的特徵是:LIFO(Last In, First Out, 後進先出)。好比咱們往箱子裏面放衣服,先放入的在最下方,只有拿出後來放入的才能拿到下方的衣服。棧中只存放基本類型和對象的引用(不是對象)。app

  • 堆(heap)堆內存用於存放由new建立的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。JVM只有一個堆區(heap)被全部線程共享,堆中不存放基本類型和對象引用,只存放對象自己。異步

  • 方法區(method):又叫靜態區,跟堆同樣,被全部的線程共享。方法區包含全部的class和static變量。函數

內存泄露緣由分析

在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種級別由高到低依次爲:強引用、軟引用、弱引用和虛引用。
Java/Android引用類型及其使用分析

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)
與SoftReference或WeakReference相比,PhantomReference主要差異體如今以下幾點:
1.PhantomReference只有一個構造函數

PhantomReference(T referent, ReferenceQueue<? super T> q)

2.無論有無強引用指向PhantomReference的指示對象,PhantomReference的get()方法返回結果都是null。

所以,PhantomReference使用必須結合ReferenceQueue;
與WeakReference相同,PhantomReference並不會改變其指示對象的垃圾回收時機。

 

內存泄露緣由

若是持有對象的強引用,垃圾回收器是沒法在內存中回收這個對象。

內存泄露的真因是:持有對象的強引用,且沒有及時釋放,進而形成內存單元一直被佔用,浪費空間,甚至可能形成內存溢出!

其實在Android中會形成內存泄露的情景無外乎兩種:
  • 全局進程(process-global)的static變量。這個無視應用的狀態,持有Activity的強引用的怪物。
  • 活在Activity生命週期以外的線程。沒有清空對Activity的強引用。

檢查一下你的項目中是否有如下幾種狀況:

  • Static Activities
  • Static Views
  • Inner Classes
  • Anonymous Classes
  • Handler
  • Threads
  • TimerTask
  • Sensor Manager

詳解見該文章《Android內存泄漏的八種可能》

最後推薦一個可檢測app內存泄露的項目:LeakCanary(能夠檢測app的內存泄露)

總結

雖然如今手機內存在不停的提高,內存泄露興許不會像dalvik時代因爲虛擬機內存太小形成各類花樣oom。可是過量的內存泄露依然會形成內存溢出,影響用戶體驗,在現在定製系統層出不窮、機型花樣愈來愈多的狀況下解決好內存泄露的問題會讓適配和穩定性進一步提升!

 

AsyncTask

若是AsyncTask被聲明爲Activity的非靜態的內部類,那麼AsyncTask會保留一個對Activity的引用。若是Activity已經被銷燬,AsyncTask的後臺線程還在執行,它將繼續在內存裏保留這個引用,致使Activity沒法被回收,引發內存泄漏。

相關文章
相關標籤/搜索