轉自http://blog.csdn.net/u013495603/article/details/50696170java
內存管理的目的就是讓咱們在開發中怎麼有效的避免咱們的應用出現內存泄漏的問題。內存泄漏你們都不陌生了,簡單粗俗的講,就是該被釋放的對象沒有釋放,一直被某個或某些實例所持有卻再也不被使用致使 GC 不能回收。最近本身閱讀了大量相關的文檔資料,打算作個 總結 沉澱下來跟你們一塊兒分享和學習,也給本身一個警示,之後 coding 時怎麼避免這些狀況,提升應用的體驗和質量。 我會從 java 內存泄漏的基礎知識開始,並經過具體例子來講明 Android 引發內存泄漏的各類緣由,以及如何利用工具來分析應用內存泄漏,最後再作總結。 篇幅有些長,你們能夠分幾節來看!android
Java 程序運行時的內存分配策略有三種,分別是靜態分配,棧式分配,和堆式分配,對應的,三種存儲策略使用的內存空間主要分別是靜態存儲區(也稱方法區)、棧區和堆區。 靜態存儲區(方法區):主要存放靜態數據、全局 static 數據和常量。這塊內存在程序編譯時就已經分配好,而且在程序整個運行期間都存在。 棧區 :當方法被執行時,方法體內的局部變量都在棧上建立,並在方法執行結束時這些局部變量所持有的內存將會自動被釋放。由於棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。 堆區 : 又稱動態內存分配,一般就是指在程序運行時直接 new 出來的內存。這部份內存在不使用時將會由 Java 垃圾回收器來負責回收。 棧與堆的區別: 在方法體內定義的(局部變量)一些基本類型的變量和對象的引用變量都是在方法的棧內存中分配的。當在一段方法塊中定義一個變量時,Java 就會在棧中爲該變量分配內存空間,當超過該變量的做用域後,該變量也就無效了,分配給它的內存空間也將被釋放掉,該內存空間能夠被從新使用。 堆內存用來存放全部由 new 建立的對象(包括該對象其中的全部成員變量)和數組。在堆中分配的內存,將由 Java 垃圾回收器來自動管理。在堆中產生了一個數組或者對象後,還能夠在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,這個特殊的變量就是咱們上面說的引用變量。咱們能夠經過這個引用變量來訪問堆中的對象或者數組。git
舉個例子: 程序員
Java的內存管理就是對象的分配和釋放問題。在 Java 中,程序員須要經過關鍵字 new 爲每一個對象申請內存空間 (基本類型除外),全部的對象都在堆 (Heap)中分配空間。另外,對象的釋放是由 GC 決定和執行的。在 Java 中,內存的分配是由程序完成的,而內存的釋放是由 GC 完成的,這種收支兩條線的方法確實簡化了程序員的工做。但同時,它也加劇了JVM的工做。這也是 Java 程序運行速度較慢的緣由之一。由於,GC 爲了可以正確釋放對象,GC 必須監控每個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC 都須要進行監控。 監視對象狀態是爲了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象再也不被引用。 爲了更好理解 GC 的工做原理,咱們能夠將對象考慮爲有向圖的頂點,將引用關係考慮爲圖的有向邊,有向邊從引用者指向被引對象。另外,每一個線程對象能夠做爲一個圖的起始頂點,例如大多程序從 main 進程開始執行,那麼該圖就是以 main 進程頂點開始的一棵根樹。在這個有向圖中,根頂點可達的對象都是有效對象,GC將不回收這些對象。若是某個對象 (連通子圖)與這個根頂點不可達(注意,該圖爲有向圖),那麼咱們認爲這個(這些)對象再也不被引用,能夠被 GC 回收。 如下,咱們舉一個例子說明如何用有向圖表示內存管理。對於程序的每個時刻,咱們都有一個有向圖表示JVM的內存分配狀況。如下右圖,就是左邊程序運行到第6行的示意圖。 github
在Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特色,首先,這些對象是可達的,即在有向圖中,存在通路能夠與其相連;其次,這些對象是無用的,即程序之後不會再使用這些對象。若是對象知足這兩個條件,這些對象就能夠斷定爲Java中的內存泄漏,這些對象不會被GC所回收,然而它卻佔用內存。 在C++中,內存泄漏的範圍更大一些。有些對象被分配了內存空間,而後卻不可達,因爲C++中沒有GC,這些內存將永遠收不回來。在Java中,這些不可達的對象都由GC負責回收,所以程序員不須要考慮這部分的內存泄露。 經過分析,咱們得知,對於C++,程序員須要本身管理邊和頂點,而對於Java程序員只須要管理邊就能夠了(不須要管理頂點的釋放)。經過這種方式,Java提升了編程的效率。 算法
集合類若是僅僅有添加元素的方法,而沒有相應的刪除機制,致使內存被佔用。若是這個集合類是全局性的變量 (好比類中的靜態屬性,全局性的 map 等即有靜態引用或 final 一直指向它),那麼沒有相應的刪除機制,極可能致使集合所佔用的內存只增不減。好比上面的典型例子就是其中一種狀況,固然實際上咱們在項目中確定不會寫這麼爛的代碼,但稍不注意仍是很容易出現這種狀況,好比咱們都喜歡經過 HashMap 作一些緩存之類的事,這種狀況就要多留一些心眼。編程
因爲單例的靜態特性使得其生命週期跟應用的生命週期同樣長,因此若是使用不恰當的話,很容易形成內存泄漏。好比下面一個典型的例子, api
正確的方式應該改成下面這種方式: 數組
非靜態內部類建立靜態實例形成的內存泄漏緩存
有的時候咱們可能會在啓動頻繁的Activity中,爲了不重複建立相同的數據資源,可能會出現這種寫法:
Handler 的使用形成的內存泄漏問題應該說是最爲常見了,不少時候咱們爲了不 ANR 而不在主線程進行耗時操做,在處理網絡任務或者封裝一些請求回調等api都藉助Handler來處理,但 Handler 不是萬能的,對於 Handler 的使用代碼編寫一不規範即有可能形成內存泄漏。另外,咱們知道 Handler、Message 和 MessageQueue 都是相互關聯在一塊兒的,萬一 Handler 發送的 Message 還沒有被處理,則該 Message 及發送它的 Handler 對象將被線程 MessageQueue 一直持有。 因爲 Handler 屬於 TLS(Thread Local Storage) 變量, 生命週期和 Activity 是不一致的。所以這種實現方式通常很難保證跟 View 或者 Activity 的生命週期保持一致,故很容易致使沒法正確釋放。 舉個例子:
這裏修復的方法是: 不要在類初始時初始化靜態成員。能夠考慮lazy初始化。 架構設計上要思考是否真的有必要這樣作,儘可能避免。若是架構須要這麼設計,那麼此對象的生命週期你有責任管理起來。
一、finalize 方法被執行的時間不肯定,不能依賴與它來釋放緊缺的資源。時間不肯定的緣由是: 二、finalize 方法只會被執行一次,即便對象被複活,若是已經執行過了 finalize 方法,再次被 GC 時也不會再執行了,緣由是: 含有 finalize 方法的 object 是在 new 的時候由虛擬機生成了一個 finalize reference 在來引用到該Object的,而在 finalize 方法執行的時候,該 object 所對應的 finalize Reference 會被釋放掉,即便在這個時候把該 object 復活(即用強引用引用住該 object ),再第二次被 GC 的時候因爲沒有了 finalize reference 與之對應,因此 finalize 方法不會再執行。 三、含有Finalize方法的object須要至少通過兩輪GC纔有可能被釋放。 虛擬機調用GC的時間不肯定 Finalize daemon線程被調度到的時間不肯定
對於使用了BraodcastReceiver,ContentObserver,File,遊標 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷,不然這些資源將不會被回收,形成內存泄漏。
有些代碼並不形成內存泄露,可是它們,或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存。 好比:
總結
對 Activity 等組件的引用應該控制在 Activity 的生命週期以內; 若是不能就考慮使用 getApplicationContext 或者 getApplication,以免 Activity 被外部長生命週期的對象引用而泄露。
儘可能不要在靜態變量或者靜態內部類中使用非靜態外部成員變量(包括context ),即便要使用,也要考慮適時把外部成員變量置空;也能夠在內部類中使用弱引用來引用外部類的變量。
對於生命週期比Activity長的內部類對象,而且內部類中使用了外部類的成員變量,能夠這樣作避免內存泄漏: 將內部類改成靜態內部類
靜態內部類中使用弱引用來引用外部類的成員變量
Handler 的持有的引用對象最好使用弱引用,資源釋放時也能夠清空 Handler 裏面的消息。好比在 Activity onStop 或者 onDestroy 的時候,取消掉該 Handler 對象的 Message和 Runnable.
在 Java 的實現過程當中,也要考慮其對象釋放,最好的方法是在不使用某對象時,顯式地將此對象賦值爲 null,好比使用完Bitmap 後先調用 recycle(),再賦爲null,清空對圖片等資源有直接引用或者間接引用的數組(使用 array.clear() ; array = null)等,最好遵循誰建立誰釋放的原則。
正確關閉資源,對於使用了BraodcastReceiver,ContentObserver,File,遊標 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷。
保持對對象生命週期的敏感,特別注意單例、靜態對象、全局性集合等的生命週期。
#歡迎你們補充優化本人GitHub相關項目https://github.com/q960757274