電話本內存泄露

1、問題背景java

測試使用monkey工具,壓力測試主題商店應用時,最後出現電話本內存泄露信息:linux

appCrashed_android.process.contacts_2016-12-30 00_34_36.txt
// CRASH: android.process.contacts (pid 5172)android


// Short Msg: java.lang.OutOfMemoryErrorgit

 

 

剛開始看到這個問題,感受一頭霧水,爲什麼測試主題商店應用,會致使電話本內存泄露呢? 這兩個看起來八竿子打不着的應用, 會存在着怎樣的糾葛,當時不知。github

項目要繼續,問題要解決。以前沒有處理過OOM問題,只好查閱資料,詢問同事協助處理。shell

 

 

2、問題分析app

 

首先想確認問題的復現機率, 測試說基本上跑個兩三個小時的monkey測試,就能復現,因而我本身也本地在嘗試,果真可否復現。嚮導主題商店的測試內容主要爲切換主題,因而就編寫腳本,工具

壓力測試切換主題,看可否使問題復現的時間縮短。測試

 

切換主題的腳本:spa

 

@echo off

adb remount
adb push 240699_Men.theme /sdcard/Themes/

pause

echo --------start to change themes-----

 ##循環切換主題, 1000次

FOR /l %%i IN (1,1,1000) DO (
adb shell am startservice -n com.nearme.themespace/com.nearme.themespace.services.ThemeApplyService --es THEME_PATH /sdcard/Themes/240699_Men.theme

##每次間隔8秒鐘
ping -n 8 127.0.0.1>nul
echo %%i
)
pause

 

 

經過腳本運行,果真復現時間縮短了不少,很快就能復現。

 

接下來就是查找哪裏出現的問題了,在網上找到一篇老外分析短信內存泄露的方法,分析中有如何使用腳本工具獲取內存信息,很好用:

http://stevevallay.github.io/blog/2014/11/17/memory-leak/

文中說明爲了獲取內存信息,咱們須要用到linux提供的 

procrank
procmem

這兩個工具,通常咱們的手機中不會自帶這兩個工具,須要去編譯的版本中查找拷貝過來使用。

 

具體項目工具適配方法:
一、因爲procmem和 Procrank 工具在咱們的手機中通常沒有放置,所以在調試各個項目時須要到各個項目編譯的out下面的去找到對應的工具以及so庫拷貝到目錄中進行替換使用,
目錄根據機器是32位仍是 64位 有所不一樣:
out\target\product\msm8952_64\system\lib
out\target\product\msm8952_64\system\lib64


二、bat腳本也須要根據32位和64位push so庫到對應目錄, shell腳本須要根據須要調試的應用進程進行修改:

 

 

放置工具到手機中的bat腳本以下:

adb remount

adb shell rm -rf /sdcard/mem_info_log/

adb push libpagemap.so /system/lib64

adb push procrank /system/xbin
adb push procmem /system/xbin

adb shell chmod 777 /system/xbin/procrank
adb shell chmod 777 /system/xbin/procmem

pause

###放置到手機後臺運行導出內存信息的shell腳本

adb push log.sh /system/bin

adb shell chmod 777 /system/bin/log.sh

adb shell sh /system/bin/log.sh &

pause

 

 

在手機中運行的shell腳本文件參照老外的寫法以下:

#!/system/bin/sh
set `ps| grep android.process.contacts`
pidOfMms=$2
echo "pid of contacts is: $pidOfMms"

set `ps|grep com.nearme.themespace`
pidOfPhone=$2
echo "pid of themespace is: $pidOfPhone"

if [ ! -d "/sdcard/mem_info_log" ]; then
mkdir "/sdcard/mem_info_log"
echo "create new dir /sdcard/mem_info_log"
fi

while [ -d "/sdcard/mem_info_log" ]
do
echo "start capture mem_info_log"
t=`date +"%Y-%m-%d-%H-%M-%S"`
echo $$ > /sdcard/mem_info_log/pid.log
procrank > "/sdcard/mem_info_log/procrank"$t".log"
procmem -p $pidOfMms > "/sdcard/mem_info_log/procmem.contacts"$t".log"
procmem -p $pidOfPhone > "/sdcard/mem_info_log/procmem.themespace"$t".log"
dumpsys meminfo android.process.contacts > "/sdcard/mem_info_log/meminfo.contacts"$t".log"
dumpsys meminfo com.nearme.themespace > "/sdcard/mem_info_log/meminfo.themespace"$t".log"
cat /proc/$pidOfPhone/maps > "/sdcard/mem_info_log/maps.themespace"$t".log"
cat /proc/$pidOfMms/maps > "/sdcard/mem_info_log/maps.contacts"$t".log"
sleep 300
done

 

腳本中每隔五分鐘會使用 

procrank
procmem

工具分別導出手機在壓力測試過程當中當前的內存信息,造成一個個文本放置在手機的一個目錄下面,便於後續分析。

 

其中最終看到的有用信息在memInfo中:

Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 4160 3992 0 0 7936 6563 1372
Dalvik Heap 13589 13464 0 0 23033 22945 88
Dalvik Other 635 632 0 0
Stack 212 212 0 0
Ashmem 6 4 0 0
Other dev 4 0 4 0
.so mmap 797 80 28 0
.apk mmap 479 0 116 0
.ttf mmap 11 0 0 0
.dex mmap 3644 8 3632 0
.oat mmap 2844 0 796 0
.art mmap 1716 940 0 0
Other mmap 96 8 16 0
Unknown 110 104 0 0
TOTAL 28303 19444 4592 0 30969 29508 1460

App Summary
Pss(KB)
------
Java Heap: 14404
Native Heap: 3992
Code: 4660
Stack: 212
Graphics: 0
Private Other: 768
System: 4267

TOTAL: 28303 TOTAL SWAP (KB): 0

Objects
Views: 389 ViewRootImpl: 1
AppContexts: 7 Activities: 4
Assets: 3 AssetManagers: 2
Local Binders: 62 Proxy Binders: 27
Parcel memory: 9 Parcel count: 36
Death Recipients: 0 OpenSSL Sockets: 0

SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0

 

看到了Activities的應用在持續增加,這樣就可以確認應用確實存在OOM了,因而開始查對應Activity裏面的代碼。

最後發如今一個activity中存在了註冊觀察者處理,可是沒有註銷,其註冊方式爲:

getActivity().getContentResolver().registerContentObserver

問題的巧合之處就是本來正常的activity的生命週期其實每次就一次onCreate, 註冊的調用是在這個方法中,若是是反覆直接進入這個activity, 最多也就有一次引用,

由於再次進入是直接運行onstart的生命週期,不會在走入onCreate中了,因此若是反覆測試電話本應用自己,此問題復現不了,反而是因爲切換主題後,致使電話本又從新運行了

onCreate方法,這樣每次都會運行註冊,增長引用次數,致使後面出現引用過多,activity遲遲不能回收後致使OOM, 使用腳本觀察,發現通常在15次左右就會出現OOM, 此時

電話本應用的內存已經接近120M, 這應該也是系統分配給一個應用的最大內存使用數值。

 

 

3、問題解決

 

固然發現問題緣由後,解決起來就比較容易了,在onDestroy中增長註銷便可:

getActivity().getContentResolver().unregisterContentObserver

 

 

隨後再網上查閱了一下經常使用的OOM問題:

https://juejin.im/entry/5762b1d7816dfa00544680a0

 我這個問題就是屬於

監聽器沒有註銷形成的內存泄漏

因此註冊和註銷必定要記得成對使用。

 

問題事後又細想了一下爲什麼這種註冊沒有註銷會致使OOM, 因而猜想這種監聽器的底層實現應該是使用了靜態static 列表,將每一個註冊傳遞過來的activity或者其餘參數

放入靜態列表中,因此會致使沒法回收。 並非全部的用getActivity()做爲參數傳遞過去以後都會致使OOM,好比初始化中獲取資源文件,有時候也會使用這個方法傳遞參數

做爲context, 這種就是沒有問題的:

FeatureOption.init(getActivity());

 

不要風聲鶴唳,之後全部的地方都不敢獲取activity 傳遞參數了。

相關文章
相關標籤/搜索