1. 程式人生 > >android基礎--記憶體洩漏

android基礎--記憶體洩漏

Android(Java)中常見的容易引起記憶體洩漏的不良程式碼:

1. 查詢資料庫沒有關閉遊標

              程式中經常會進行查詢資料庫的操作,但是經常會有使用完畢Cursor後沒有關閉的情況。如果我們的查詢結果集比較小,對記憶體的消耗不容易被發現,只有在常時間大量操作的情況下才會復現記憶體問題,這樣就會給以後的測試和問題排查帶來困難和風險      示例代如下碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

Cursor cursor = getContentResolver().query(uri ...);

if (cursor.moveToNext()) {

  ... ...  

}

修正示例程式碼:

Cursor cursor = null;

try {  

  cursor = getContentResolver().query(uri ...);

  if (cursor != null && cursor.moveToNext()) {

  ... ...  

  }

} finally {

  if (cursor != null) {

      try {  

          cursor.close();

      } catch (Exception e) {

          //ignore this

      }

  }

}

 

 2. 構造Adapter時,沒有使用快取的 convertView 

3. Bitmap物件不再使用時呼叫recycle()沒有及時釋放 

    如果一個Bitmap物件比較佔記憶體,當它不在被使用的時候,可以呼叫Bitmap.recycle()方法回收此物件的畫素所佔用的記憶體

4.沒有及時釋放物件的引用


    簡單舉個例子:

        比如兩個Activity之間傳遞的Context 或其它的自定義物件,使用完後必須立即釋放

        即:Activity = null ;    Context = null ; Object = null;

        可以的話在這釋放物件之後通知系統來回收:System.gc();

        Android主要應用在嵌入式裝置當中,而嵌入式裝置由於一些眾所周知的條件限制,通常都不會有很高的配置,特別是記憶體是比較有限的。如果我們編寫的代 碼當中有太多的對記憶體使用不當的地方,難免會使得我們的裝置執行緩慢,甚至是宕機。

        為了能夠使得Android應用程式安全且快速的執行,Android 的每個應用程式都會使用一個專有的Dalvik虛擬機器例項來執行,它是由Zygote服務程序演變過來的,也就是說每個應用程式都是在屬於自己的程序中執行的。

        一方面,如果程式在執行過程中出現了記憶體洩漏的問題,僅僅會使得自己的程序被殺掉,而不會影響其他程序(如果是system_process 等系統程序出問題的話,則會引起系統重啟)。

        另一方面Android為不同型別的程序分配了不同的記憶體使用上限,如果應用程序使用的記憶體超過了這個上限, 則會被系統視為記憶體洩漏,從而被殺掉

        每個應用程式都是在屬於自己的程序中執行的,當一個程式第一次啟動的時候,Android會啟動一個LINUX程序和一個主執行緒。預設的情況下,所有該程式的元件都將在該程序和執行緒中執行。

        同時,Android會為每個應用程式分配一個單獨的LINUX使用者。Android會盡量保留一個正在執行程序,只在記憶體資源出現不足時,Android會嘗試停止一些程序從而釋放足夠的資源給其他新的程序使用, 也能保證使用者正在訪問的當前程序有足夠的資源去及時地響應使用者的事件。

        Android會根據程序中執行的元件類別以及元件的狀態來判斷該程序的重要性,Android會首先停止那些不重要的程序。按照重要性從高到低一共有五個級別就是我們常說的:前臺程序、可見程序、服務程序、後臺程序、空程序

        當一個程式第一次啟動時,Android會同時啟動一個對應的主執行緒(Main Thread),主執行緒主要負責處理與UI相關的事件,如使用者的按鍵事件,使用者接觸螢幕的事件以及螢幕繪圖事件,並把相關的事件分發到對應的元件進行處理。所以主執行緒通常又被叫做UI執行緒。

        在開發Android應用時必須遵守單執行緒模型的原則: Android UI操作並不是執行緒安全的並且這些操作必須在UI執行緒中執行。Android的UI是單執行緒(Single-threaded)的。為了避免拖住GUI,一些較費時的物件應該交給獨立的執行緒去執行。如果幕後的執行緒來執行UI物件,Android就會發出錯誤訊息 CalledFromWrongThreadException。

        下面直接進入主題了:OOM除錯

方式一:使用記憶體監測工具 DDMS –> Heap:(真機、模擬器均可使用)

        1. 啟動eclipse後,切換到DDMS透檢視,並確認Devices檢視、Heap檢視都是開啟的,沒開啟的直接Window>ShowView>自己選;
        2. 將手機通過USB連結至電腦,連結時需要確認手機是處於“USB除錯”模式

        3. 連結成功後,在DDMS的Devices檢視中將會顯示手機裝置的序列號,以及裝置中正在執行的部分程序資訊;
        4. 點選選中想要監測的程序,如果在程序列表中未出現你的程序的話隨便選中一條讓Device一排的工具處於可用狀態,再點選下Update Heap讓其自動找到我們跑的應用的程序,

比如下圖臨時跑的兩個應用程序如圖;

5. 點選Heap檢視中的“Cause GC”按鈕;

  6.點選Cause GC之後就可以看到我們應用的記憶體情況如下圖:

說明:
a) 點選“Cause GC”按鈕相當於向虛擬機器請求了一次gc操作;
b) 當記憶體使用資訊第一次顯示以後,無須再不斷的點選“Cause GC”,Heap檢視介面會定時重新整理,在對應用的不斷的操作過程中就可以看到記憶體使用的變化;
c) 記憶體使用資訊的各項引數根據名稱即可知道其意思

        那麼如何才能知道我們的程式是否有記憶體洩漏的可能性呢。這裡需要注意一個值:

        Heap檢視中部有一個Type叫做data object,即資料物件,也就是我們的程式中大量存在的類型別的物件。在data object一行中有一列是“Total Size”,其值就是當前程序中所有Java資料物件的記憶體總量,如果大家想要看“Total Size”是分配的具體資訊可以點選“data object這一行來檢視詳細資訊,如下圖”(大家看不清楚的點選看大圖)

一般情況下,在data object行的“Total Size”這個值的大小決定了是否會有記憶體洩漏。可以這樣判斷:
        a: 不斷的操作當前應用,同時注意觀察data object的Total Size值;
        b: 正常情況下Total Size值都會穩定在一個有限的範圍內,也就是說由於程式中的的程式碼良好,沒有造成物件不被垃圾回收的情況,所以說雖然我們不斷的操作會不斷的生成很多對 象,而在虛擬機器不斷的進行GC的過程中,這些物件都被回收了,記憶體佔用量會會落到一個穩定的水平;
        c: 反之如果程式碼中存在沒有釋放物件引用的情況,則data object的Total Size值在每次GC後不會有明顯的回落,隨著操作次數的增多Total Size的值會越來越大,直到到達一個上限後導致程序被殺掉。

 Android為應用程序分配的記憶體上限如下所示:
        位置: /ANDROID_SOURCE/system/core/rootdir/init.rc 部分指令碼
# Define the oom_adj values for the classes of processes that can be
# killed by the kernel. These are used in ActivityManagerService.
  setprop ro.FOREGROUND_APP_ADJ 0
  setprop ro.VISIBLE_APP_ADJ 1
  setprop ro.SECONDARY_SERVER_ADJ 2
  setprop ro.BACKUP_APP_ADJ 2
  setprop ro.HOME_APP_ADJ 4
  setprop ro.HIDDEN_APP_MIN_ADJ 7
  setprop ro.CONTENT_PROVIDER_ADJ 14
  setprop ro.EMPTY_APP_ADJ 15
# Define the memory thresholds at which the above process classes will
# be killed. These numbers are in pages (4k).
  setprop ro.FOREGROUND_APP_MEM 1536
  setprop ro.VISIBLE_APP_MEM 2048
  setprop ro.SECONDARY_SERVER_MEM 4096
  setprop ro.BACKUP_APP_MEM 4096
  setprop ro.HOME_APP_MEM 4096
  setprop ro.HIDDEN_APP_MEM 5120
  setprop ro.CONTENT_PROVIDER_MEM 5632
  setprop ro.EMPTY_APP_MEM 6144
# Write value must be consistent with the above properties.
# Note that the driver only supports 6 slots, so we have HOME_APP at the
# same memory level as services.
  write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15
  write /proc/sys/vm/overcommit_memory 1
  write /proc/sys/vm/min_free_order_shift 4
  write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144
  # Set init its forked children’s oom_adj.
  write /proc/1/oom_adj -16

     d: 此處以上圖程序為例,在我的測試環境中com.xiaoma.www程序所佔用的記憶體的data object的Total Size正常情況下穩定在0.8~1.0M之間,而當其值超過3~5M每次啟動應用該值不穩定的時候程序就會被系統殺掉啦!

方式二:

記憶體監測工具 DDMS –> Heap 
使用記憶體分析工具 MAT(Memory Analyzer Tool) 
(一) 生成.hprof檔案 (生成很簡單,直接點選Device 工具欄中的 Dump HPROF file即可生成)
(二) 使用MAT匯入.hprof檔案 
(三) 使用MAT的檢視工具分析記憶體
 

      總之,使用DDMS的Heap檢視工具可以很方便的確認我們的程式是否存在記憶體洩漏的可能性。

這個地方順帶著也簡單的說一下,如果大家想要跟蹤更詳細的記憶體是怎樣分配的話,可以學著使用下Windows>ShowView>Allocation Tracker工具來定位你的記憶體什麼時候被什麼東西佔用了,是bundlea或HashMap或ArrayList焦點或是其它的什麼佔用了這些都可以在這個外掛中檢視到的,簡單的使用方法就是:選中Device程序列表中的某一個程序,讓Allocation Tracker裡面的跟蹤工具處於可用狀態,點選Stop Tracking來跟蹤程式,過一定時間後,點選Get Allocations來獲取記憶體分配的訊息就可以檢視更為詳細的記憶體分配情況了,如下圖: