內存泄露是如何產生的?java
當一個對象已經不須要再使用了,本該被回收時,而有另一個正在使用的對象持有它的引用從而致使它不能被回收,這致使本該被回收的對象不能被回收而停留在堆內存中,這就產生了內存泄漏。程序員
內存泄露是形成OOM的主要緣由之一。windows
內存泄露的對象是什麼?數組
內存分配有三種策略:靜態(靜態存儲區/方法區)、棧、堆。app
靜態存儲區(方法區):內存在程序編譯的時候就已經分配好,這塊內存在程序整個運行期間都存在。它主要存放靜態數據、全局static數據和常量。異步
棧區:在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。ide
堆區:亦稱動態內存分配。程序在運行的時候用malloc或new申請任意大小的內存,程序員本身負責在適當的時候用free或delete釋放內存(Java則依賴垃圾回收器)。動態內存的生存期能夠由咱們決定,若是咱們不釋放內存,程序將在最後才釋放掉動態內存。函數
堆和棧的區別:工具
棧:定義一些基本數據類型變量和應用變量。當在一段代碼塊中定義一個變量時,java就在棧中爲這個變量分配內存空間,當超過變量的做用域後,java會自動釋放掉爲該變量分配的內存空間,該內存空間能夠馬上被另做他用。oop
堆:用於存放全部由new建立的對象(內容包括該對象其中的全部成員變量)和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象後,還能夠在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,之後就能夠在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量至關於爲數組或者對象起的一個別名,或者代號。
堆是不連續的內存區域(由於系統是用鏈表來存儲空閒內存地址,天然不是連續的),堆大小受限於計算機系統中有效的虛擬內存(32bit系統理論上是4G),因此堆的空間比較靈活,比較大。棧是一塊連續的內存區域,大小是操做系統預約好的,windows下棧大小是2M(也有是1M,在編譯時肯定,VC中可設置)。
對於堆,頻繁的new/delete會形成大量內存碎片,使程序效率下降。對於棧,它是先進後出的隊列,進出一一對應,不產生碎片,運行效率穩定高。
結論:
局部變量的基本數據類型和引用存儲於棧中,引用的對象實體存儲於堆中。
——由於它們屬於方法中的變量,生命週期隨方法而結束。
成員變量所有存儲與堆中(包括基本數據類型,引用和引用的對象實體)
——由於它們屬於類,類對象終究是要被new出來使用的。
內存爲何會泄露?
Java的內存管理就是對象的分配和釋放問題。在Java中,內存的分配是由程序完成的,而內存的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程序員不須要經過調用函數來釋放內存,但它只能回收無用而且再也不被其它對象引用的那些對象所佔用的空間。
堆內存中的長生命週期的對象持有短生命週期對象的強/軟引用,儘管短生命週期對象已經再也不須要,可是由於長生命週期對象持有它的引用而致使不能被回收,這就是Java中內存泄露的根本緣由。
Java內存回收機制:
從程序的主要運行對象(如靜態對象/寄存器/棧上指向的堆內存對象等)開始檢查引用鏈,當遍歷一遍後獲得上述這些沒法回收的對象和他們所引用的對象鏈,組成沒法回收的對象集合,而其餘孤立對象(集)就做爲垃圾回收。GC爲了可以正確釋放對象,必須監控每個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都須要進行監控。監視對象狀態是爲了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象再也不被引用。
關於「引用」:通俗的講,經過A能調用並訪問到B,那就說明A持有B的引用,或A就是B的引用。
Java中對引用的分類:
在Android應用的開發中,爲了防止內存溢出,在處理一些佔用內存大並且聲明週期較長的對象時候,能夠儘可能應用軟引用和弱引用技術。
軟/弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。利用這個隊列能夠得知被回收的軟/弱引用的對象列表,從而爲緩衝器清除已失效的軟/弱引用。
常見的內存泄露狀況:
一、集合類泄露
集合類若是僅僅有添加元素的方法,而沒有相應的刪除機制,致使內存被佔用。若是這個集合類是全局性的變量 (好比類中的靜態屬性,全局性的 map 等即有靜態引用或 final 一直指向它),那麼沒有相應的刪除機制,極可能致使集合所佔用的內存只增不減。
二、非靜態內部類建立靜態實例形成內存泄漏。
修復方法:將該內部類設爲靜態內部類或將該內部類抽取出來封裝成一個單例,若是須要使用Context,推薦的使用Application 的 Context。
匿名內部類有可能會持有當前Activity實例,若是將這個引用傳入到異步線程,此線程和Activity生命週期不一致是,將會形成內存泄露。
三、Handler 形成的內存泄漏
Handler 發送的 Message 還沒有被處理,則該 Message 及發送它的 Handler 對象將被線程 MessageQueue 一直持有,且Handler的聲明週期和Activity的不一致,容易致使內存沒法被正確釋放。
修復方法:在Activity中避免使用非靜態內部類,即將Handler改成static,此時Handler的存活週期將於Activity無關,同事以弱應用的方式引入Activity,避免直接將Activity做爲context傳入。每次使用前需判空。
Looper 線程的消息隊列中仍是可能會有待處理的消息,因此咱們在 Activity 的 Destroy 時或者 Stop 時應該移除消息隊列 MessageQueue 中的消息。
public class MainActivity extends AppCompatActivity { private MyHandler mHandler = new MyHandler(this); private TextView mTextView ; private static class MyHandler extends Handler { private WeakReference reference; public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if(activity != null){ activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView)findViewById(R.id.textview); loadData(); } private void loadData() { //...request Message message = Message.obtain(); mHandler.sendMessage(message); } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); } }
四、儘可能避免使用 static 成員變量
若是成員變量被聲明爲 static,那咱們都知道其生命週期將與整個app進程生命週期同樣。將會致使app常駐內存,從而致使app頻繁重啓,形成耗電、耗流量。
修復方法:不要在類初始時初始化靜態成員。能夠考慮lazy初始化。
五、避免 override finalize()
finalize 方法被執行的時間不肯定,finalize 方法只會被執行一次,即便對象被複活,若是已經執行過了 finalize 方法,再次被 GC 時也不會再執行了,含有Finalize方法的object須要至少通過兩輪GC纔有可能被釋放。
六、線程泄露
線程也是形成內存泄露的一個重要的源頭。線程產生內存泄露的主要緣由在於線程生命週期的不可控。好比線程是 Activity 的內部類,則線程對象中保存了 Activity 的一個引用,當線程的 run 函數耗時較長沒有結束時,線程對象是不會被銷燬的,所以它所引用的老的 Activity 也不會被銷燬,所以就出現了內存泄露的問題。
六、資源未關閉形成的內存泄漏
應在Activity被銷燬時及時關閉或註銷BraodcastReceiver,ContentObserver,File,遊標 Cursor,Stream,Bitmap等資源。
七、在 Java 的實現過程當中,也要考慮其對象釋放,最好的方法是在不使用某對象時,顯式地將此對象賦值爲 null,好比使用完Bitmap 後先調用 recycle(),再賦爲null,清空對圖片等資源有直接引用或者間接引用的數組(使用 array.clear() ; array = null)等,最好遵循誰建立誰釋放的原則。
八、單例形成的內存泄露
因爲單例的靜態特性使得單例的生命週期和應用的生命週期同樣長,若是一個對象已經不須要使用了,而單例對象還持有該對象的引用,則致使這個對象不能被正常回收,形成內存泄露。
解決辦法:
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext();// 使用Application 的context } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; } }
或者如下寫法,連Context都不用傳進來了
... context = getApplicationContext(); ... /** * 獲取全局的context * @return 返回全局context對象 */ public static Context getContext(){ return context; } public class AppManager { private static AppManager instance; private Context context; private AppManager() { this.context = MyApplication.getContext();// 使用Application 的context } public static AppManager getInstance() { if (instance != null) { instance = new AppManager(); } return instance; } }
檢測程序中內存泄露的工具:LeakCanary、MAT等
一、對於生命週期比Activity長的對象若是須要應該使用ApplicationContext
二、對於須要在靜態內部類中使用非靜態外部成員變量(如:Context、View ),能夠在靜態內部類中使用弱引用來引用外部類的變量來避免內存泄漏
三、對於再也不須要使用的對象,顯示的將其賦值爲null,好比使用完Bitmap後先調用recycle(),再賦爲null
四、保持對對象生命週期的敏感,特別注意單例、靜態對象、全局性集合等的生命週期
五、對於生命週期比Activity長的內部類對象,而且內部類中使用了外部類的成員變量,能夠這樣作避免內存泄漏:
(1)將內部類改成靜態內部類
(2)靜態內部類中使用弱引用來引用外部類的成員變量
六、在涉及到Context時先考慮ApplicationContext,固然它並非萬能的,對於有些地方則必須使用Activity的Context,對於Application,Service,Activity三者的Context的應用場景以下:
參考文獻:
http://dev.qq.com/topic/59152c9029d8be2a14b64dae