編輯:關於Android編程
java內存洩漏大家都不陌生了,簡單粗俗的講,就是該被釋放的對象沒有釋放,一直被某個或某些實例所持有卻不再被使用導致 GC 不能回收。在Java中,內存洩漏就是存在一些被分配的對象,這些對象有下面兩個特點,首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連;其次,這些對象是無用的,即程序以後不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的內存洩漏,這些對象不會被GC所回收,然而它卻占用內存。
在C++中,內存洩漏的范圍更大一些。有些對象被分配了內存空間,然後卻不可達,由於C++中沒有GC,這些內存將永遠收不回來。在Java中,這些不可達的對象都由GC負責回收,因此程序員不需要考慮這部分的內存洩露。
通過分析,我們得知,對於C++,程序員需要自己管理邊和頂點,而對於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 中,如果我們僅僅釋放引用本身,那麼 Vector 仍然引用該對象,所以這個對象對 GC 來說是不可回收的。因此,如果對象加入到Vector 後,還必須從 Vector 中刪除,最簡單的方法就是將 Vector 對象設置為 null。
Android內存洩漏的主要問題可以分為以下幾種類型
一、靜態變量引起的內存洩漏
在java中靜態變量的生命周期是在類加載時開始,類卸載時結束。換句話說,在android中其生命周期是在進程啟動時開始,進程死亡時結束。所以在程序的運行期間,如果進程沒有被殺死,靜態變量就會一直存在,不會被回收掉。如果靜態變量強引用了某個Activity中變量,那麼這個Activity就同樣也不會被釋放,即便是該Activity執行了onDestroy(不要將執行onDestroy和被回收劃等號)。這類問題的解決方案為:1.尋找與該靜態變量生命周期差不多的替代對象。2.若找不到,將強引用方式改成弱引用。例子:
1、單例引起的Context內存洩漏
由於單例的靜態特性使得其生命周期跟應用的生命周期一樣長,所以如果使用不恰當的話,很容易造成內存洩漏。
public class IMManager { private Context context; private static IMManager mInstance; public static IMManager getInstance(Context context) { if (mInstance == null) { synchronized (IMManager.class) { if (mInstance == null) mInstance = new IMManager(context); } } return mInstance; } private IMManager(Context context) { this.context = context; } }
這是一個普通的單例模式,當創建這個單例的時候,由於需要傳入一個Context,所以這個Context的生命周期的長短至關重要:
1、如果此時傳入的是 Application 的 Context,因為 Application 的生命周期就是整個應用的生命周期,所以這將沒有任何問題。
2、如果此時傳入的是 Activity 的 Context,當這個 Context 所對應的 Activity 退出時,由於該 Context 的引用被單例對象所持有,其生命周期等於整個應用程序的生命周期,所以當前 Activity 退出時它的內存並不會被回收,這就造成洩漏了。
正確的方式應該改為下面這種方式:
public class IMManager { private Context context; private static IMManager mInstance; public static IMManager getInstance(Context context) { if (mInstance == null) { synchronized (IMManager.class) { if (mInstance == null) //將傳入的context轉換成Application的context mInstance = new IMManager(context.getApplicationContext()); } } return mInstance; } private IMManager(Context context) { this.context = context; } }二、非靜態內部類引起的內存洩漏
在java中,創建一個非靜態的內部類實例,就會引用它的外圍實例。
第一種情況:
如果這個非靜態內部類實例做了一些耗時的操作,就會造成外圍對象不會被回收,從而導致內存洩漏。這類問題的解決方案為:1.將內部類變成靜態內部類 2.如果有強引用Activity中的屬性,則將該屬性的引用方式改為弱引用。3.在業務允許的情況下,當Activity執行onDestory時,結束這些耗時任務。例子:
public class LeakAty extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); } public void test() { //匿名內部類會引用其外圍實例LeakAty.this,所以會導致內存洩漏 new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }解決方法:
public class LeakAty extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); //加上static,變成靜態匿名內部類 } public static void test() { new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
第二種情況:非靜態內部類創建靜態實例造成的內存洩漏
public class MainActivity extends Activity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mResource == null){ mResource = new TestResource(); } } class TestResource { } }這樣就在Activity內部創建了一個非靜態內部類的單例,每次啟動Activity時都會使用該單例的數據,這樣雖然避免了資源的重復創建,不過這種寫法卻會造成內存洩漏,因為非靜態內部類默認會持有外部類的引用,而該非靜態內部類又創建了一個靜態的實例,該實例的生命周期和應用的一樣長,這就導致了該靜態實例一直會持有該Activity的引用,導致Activity的內存資源不能正常回收。
正確做法:
將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,請按照上面推薦的使用Application 的 Context。關於Context的問題,參考:http://blog.csdn.net/rebirth_love/article/details/51853986
三、Handler引起的內存洩漏
Handler 的使用造成的內存洩漏問題應該說是最為常見了,很多時候我們為了避免 ANR 而不在主線程進行耗時操作,在處理網絡任務或者封裝一些請求回調等api都借助Handler來處理,但 Handler 不是萬能的,對於 Handler 的使用代碼編寫一不規范即有可能造成內存洩漏。另外,我們知道 Handler、Message 和 MessageQueue 都是相互關聯在一起的,萬一 Handler 發送的 Message 尚未被處理,則該 Message 及發送它的 Handler 對象將被線程 MessageQueue 一直持有。由於 Handler 屬於 TLS(Thread Local Storage) 變量, 生命周期和 Activity 是不一致的。因此這種實現方式一般很難保證跟 View 或者 Activity 的生命周期保持一致,故很容易導致無法正確釋放。例子:
public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }分析:上邊例子有三個地方容易造成內存洩漏。第一個:mLeakyHandler為匿名內部類實例,會引用外圍對象LeakAty.this,如果該Handler在Activity退出時依然還有消息需要處理,那麼這個Activity就不會被回收。第二個:new Runnable(){}非靜態匿名內部類。第三:Handler中對context的引用。
當該 Activity 被 finish() 掉時,延遲執行任務的 Message 還會繼續存在於主線程中,它持有該 Activity 的 Handler 引用,所以此時 finish() 掉的 Activity 就不會被回收了從而造成內存洩漏(因 Handler 為非靜態內部類,它會持有外部類的引用,在這裡就是指 SampleActivity)。
修復方法:在 Activity 中避免使用非靜態內部類,比如上面我們將 Handler 聲明為靜態的,則其存活期跟 Activity 的生命周期就無關了。同時通過弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進去,見下面代碼:
public class SampleActivity extends Activity { /** * Instances of static inner classes do not hold an implicit * reference to their outer class. * 靜態的內部類,則其存活期跟 Activity 的生命周期就無關了 */ private static class MyHandler extends Handler { //弱引用解決context引用引起的內存洩漏 private final WeakReferencemActivity; public MyHandler(SampleActivity activity) { mActivity = new WeakReference (activity); } @Override public void handleMessage(Message msg) { SampleActivity activity = mActivity.get(); if (activity != null) { // ... } } } private final MyHandler mHandler = new MyHandler(this); /** * Instances of anonymous classes do not hold an implicit * reference to their outer class when they are "static". * 靜態的內部類,則其存活期跟 Activity 的生命周期就無關了 */ private static final Runnable sRunnable = new Runnable() { @Override public void run() { /* ... */ } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mHandler.postDelayed(sRunnable, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
下面幾個方法都可以移除 Message:
public final void removeCallbacks(Runnable r); public final void removeCallbacks(Runnable r, Object token); public final void removeCallbacksAndMessages(Object token); public final void removeMessages(int what); public final void removeMessages(int what, Object object);四、資源未關閉造成的內存洩漏
對於使用了BraodcastReceiver,ContentObserver,File,游標 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收,造成內存洩漏。
參考文章:
https://yq.aliyun.com/articles/3009?&utm_campaign=sys&utm_medium=market&utm_source=edm_email&msctype=email&mscmsgid=111916020300373837&#
http://mp.weixin.qq.com/s?__biz=MzA3MDMyMjkzNg==&mid=404182514&idx=1&sn=6ca5df78a3f5f48b4a9f91a035690631&scene=0#wechat_redirect
補充一下java reference的知識點:
Java對引用的分類有 Strong reference, SoftReference, WeakReference, PhatomReference 四種。
在Android應用的開發中,為了防止內存溢出,在處理一些占用內存大而且聲明周期較長的對象時候,可以盡量應用軟引用和弱引用技術。
軟/弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。利用這個隊列可以得知被回收的軟/弱引用的對象列表,從而為緩沖器清除已失效的軟/弱引用。
假設我們的應用會用到大量的默認圖片,比如應用中有默認的頭像,默認游戲圖標等等,這些圖片很多地方會用到。如果每次都去讀取圖片,由於讀取文件需要硬件操作,速度較慢,會導致性能較低。所以我們考慮將圖片緩存起來,需要的時候直接從內存中讀取。但是,由於圖片占用內存空間比較大,緩存很多圖片需要很多的內存,就可能比較容易發生OutOfMemory異常。這時,我們可以考慮使用軟/弱引用技術來避免這個問題發生。以下就是高速緩沖器的雛形:
使用軟引用以後,在OutOfMemory異常發生之前,這些緩存的圖片資源的內存空間可以被釋放掉的,從而避免內存達到上限,避免Crash發生。
如果只是想避免OutOfMemory異常的發生,則可以使用軟引用。如果對於應用的性能更在意,想盡快回收一些占用內存比較大的對象,則可以使用弱引用。
另外可以根據對象是否經常使用來判斷選擇軟引用還是弱引用。如果該對象可能會經常使用的,就盡量用軟引用。如果該對象不被使用的可能性更大些,就可以用弱引用。
本文實例講述了Android利用jsoup解析HTML頁面的方法。分享給大家供大家參考,具體如下:這節主要是講解jsoup解析HTML頁面。由於在android開發過程中
本文實例講述了Android ListView的簡單應用。分享給大家供大家參考,具體如下:我們今天要講的內容是Android中ListView中的實現.一共分為四個步驟,
MainActivity如下:package cc.textview5; import android.os.Bundle; import android.text.Te
注意:以下內容中出現的類和部分類的方法只能在Android源碼中或者通過反射機制才能使用,在SDK中編譯是通不過的!!如Android.os.Service; Memeo