當前位置:編程學習大全網 - 網絡軟體 - 常見的內存泄漏原因及解決方法

常見的內存泄漏原因及解決方法

(Memory Leak,內存泄漏)

當壹個對象已經不需要再使用本該被回收時,另外壹個正在使用的對象持有它的引用從而導致它不能被回收,這導致本該被回收的對象不能被回收而停留在堆內存中,這就產生了內存泄漏。

內存泄漏是造成應用程序OOM的主要原因之壹。我們知道Android系統為每個應用程序分配的內存是有限的,而當壹個應用中產生的內存泄漏比較多時,這就難免會導致應用所需要的內存超過系統分配的內存限額,這就造成了內存溢出從而導致應用Crash。

因為內存泄漏是在堆內存中,所以對我們來說並不是可見的。通常我們可以借助MAT、LeakCanary等工具來檢測應用程序是否存在內存泄漏。

1、MAT是壹款強大的內存分析工具,功能繁多而復雜。

2、LeakCanary則是由Square開源的壹款輕量級的第三方內存泄漏檢測工具,當檢測到程序中產生內存泄漏時,它將以最直觀的方式告訴我們哪裏產生了內存泄漏和導致誰泄漏了而不能被回收。

由於單例的靜態特性使得其生命周期和應用的生命周期壹樣長,如果壹個對象已經不再需要使用了,而單例對象還持有該對象的引用,就會使得該對象不能被正常回收,從而導致了內存泄漏。

示例:防止單例導致內存泄漏的實例

這樣不管傳入什麽Context最終將使用Application的Context,而單例的生命周期和應用的壹樣長,這樣就防止了內存泄漏。?

例如,有時候我們可能會在啟動頻繁的Activity中,為了避免重復創建相同的數據資源,可能會出現如下寫法:

這樣在Activity內部創建了壹個非靜態內部類的單例,每次啟動Activity時都會使用該單例的數據。雖然這樣避免了資源的重復創建,但是這種寫法卻會造成內存泄漏。因為非靜態內部類默認會持有外部類的引用,而該非靜態內部類又創建了壹個靜態的實例,該實例的生命周期和應用的壹樣長,這就導致了該靜態實例壹直會持有該Activity的引用,從而導致Activity的內存資源不能被正常回收。

解決方法 :將該內部類設為靜態內部類或將該內部類抽取出來封裝成壹個單例,如果需要使用Context,就使用Application的Context。

示例:創建匿名內部類的靜態對象

1、從Android的角度

當Android應用程序啟動時,該應用程序的主線程會自動創建壹個Looper對象和與之關聯的MessageQueue。當主線程中實例化壹個Handler對象後,它就會自動與主線程Looper的MessageQueue關聯起來。所有發送到MessageQueue的Messag都會持有Handler的引用,所以Looper會據此回調Handle的handleMessage()方法來處理消息。只要MessageQueue中有未處理的Message,Looper就會不斷的從中取出並交給Handler處理。另外,主線程的Looper對象會伴隨該應用程序的整個生命周期。

2、 Java角度

在Java中,非靜態內部類和匿名類內部類都會潛在持有它們所屬的外部類的引用,但是靜態內部類卻不會。

對上述的示例進行分析,當MainActivity結束時,未處理的消息持有handler的引用,而handler又持有它所屬的外部類也就是MainActivity的引用。這條引用關系會壹直保持直到消息得到處理,這樣阻止了MainActivity被垃圾回收器回收,從而造成了內存泄漏。

解決方法 :將Handler類獨立出來或者使用靜態內部類,這樣便可以避免內存泄漏。

示例:AsyncTask和Runnable

AsyncTask和Runnable都使用了匿名內部類,那麽它們將持有其所在Activity的隱式引用。如果任務在Activity銷毀之前還未完成,那麽將導致Activity的內存資源無法被回收,從而造成內存泄漏。

解決方法 :將AsyncTask和Runnable類獨立出來或者使用靜態內部類,這樣便可以避免內存泄漏。

對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源,應該在Activity銷毀時及時關閉或者註銷,否則這些資源將不會被回收,從而造成內存泄漏。

1)比如在Activity中register了壹個BraodcastReceiver,但在Activity結束後沒有unregister該BraodcastReceiver。

2)資源性對象比如Cursor,Stream、File文件等往往都用了壹些緩沖,我們在不使用的時候,應該及時關閉它們,以便它們的緩沖及時回收內存。它們的緩沖不僅存在於 java虛擬機內,還存在於java虛擬機外。如果我們僅僅是把它的引用設置為null,而不關閉它們,往往會造成內存泄漏。

3)對於資源性對象在不使用的時候,應該調用它的close()函數將其關閉掉,然後再設置為null。在我們的程序退出時壹定要確保我們的資源性對象已經關閉。

4)Bitmap對象不在使用時調用recycle()釋放內存。2.3以後的bitmap應該是不需要手動recycle了,內存已經在java層了。

初始時ListView會從BaseAdapter中根據當前的屏幕布局實例化壹定數量的View對象,同時ListView會將這些View對象緩存起來。當向上滾動ListView時,原先位於最上面的Item的View對象會被回收,然後被用來構造新出現在下面的Item。這個構造過程就是由getView()方法完成的,getView()的第二個形參convertView就是被緩存起來的Item的View對象(初始化時緩存中沒有View對象則convertView是null)。

構造Adapter時,沒有使用緩存的convertView。

解決方法 :在構造Adapter時,使用緩存的convertView。

我們通常把壹些對象的引用加入到了集合容器(比如ArrayList)中,當我們不需要該對象時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。

解決方法 :在退出程序之前,將集合裏的東西clear,然後置為null,再退出程序。

當我們不要使用WebView對象時,應該調用它的destory()函數來銷毀它,並釋放其占用的內存,否則其長期占用的內存也不能被回收,從而造成內存泄露。

解決方法 :為WebView另外開啟壹個進程,通過AIDL與主線程進行通信,WebView所在的進程可以根據業務的需要選擇合適的時機進行銷毀,從而達到內存的完整釋放。

1、在涉及使用Context時,對於生命周期比Activity長的對象應該使用Application的Context。凡是使用Context優先考慮Application的Context,當然它並不是萬能的,對於有些地方則必須使用Activity的Context。對於Application,Service,Activity三者的Context的應用場景如下:

其中,NO1表示Application和Service可以啟動壹個Activity,不過需要創建壹個新的task任務隊列。而對於Dialog而言,只有在Activity中才能創建。除此之外三者都可以使用。

2、對於需要在靜態內部類中使用非靜態外部成員變量(如:Context、View ),可以在靜態內部類中使用弱引用來引用外部類的變量來避免內存泄漏。

3、對於不再需要使用的對象,顯示的將其賦值為null,比如使用完Bitmap後先調用recycle(),再賦為null。

4、保持對對象生命周期的敏感,特別註意單例、靜態對象、全局性集合等的生命周期。

5、對於生命周期比Activity長的內部類對象,並且內部類中使用了外部類的成員變量,可以這樣做避免內存泄漏:

1)將內部類改為靜態內部類

2)靜態內部類中使用弱引用來引用外部類的成員變量

Android內存泄漏總結

  • 上一篇:春節聯歡晚會劉謙手穿玻璃魔術揭密
  • 下一篇:請問江蘇衛視"非誠勿擾"節目首播和重播的時間分別是什麽時候?
  • copyright 2024編程學習大全網