Android应用虚拟内存泄漏问题分析
android系统上,各种订制修改比较多,经常会遇到一些奇怪的内存泄漏问题。最近遇到一个比较少见的应用端泄漏,这边记录一下。
最开始,就是使用Android Studio自带的利器:Profiler
应用运行大概2小时左右,抓取到的图如上,没发现有明显的内存增加,但遇到如下崩溃:
02-04 13:15:03.661 1070 1087 W libc : pthread_create failed: couldn't allocate 1069056-bytes mapped space: Out of memory
02-04 13:15:03.661 1070 1087 W art : Throwing OutOfMemoryError "pthread_create (1040KB stack) failed: Try again"
E mali_so : encounter the first mali_error : 0x0002 : failed to allocate CPU memory (gles_texturep_upload_3d_internal at hardware/arm/maliT760/driver/product/gles/src/texture/mali_gles_texture_upload.c:1030)
02-04 13:15:18.664 1070 1627 E OpenGLRenderer: GL error: Out of memory!
02-04 13:15:18.665 1070 1627 F OpenGLRenderer: GL errors! frameworks/base/libs/hwui/renderthread/CanvasContext.cpp:550
不到1M的内存都分配不出来了?通过下面命令,看起来系统还是有500多M空闲内存。
cat /proc/meminfo
MemTotal: 2045160 kB
MemFree: 529064 kB
MemAvailable: 1250020 kB
Buffers: 1300 kB
Cached: 891916 kB
SwapCached: 0 kB
Active: 556296 kB
Inactive: 674200 kB
Active(anon): 235800 kB
Inactive(anon): 224668 kB
Active(file): 320496 kB
Inactive(file): 449532 kB
Unevictable: 256 kB
Mlocked: 256 kB
那就比较奇怪了,这边写了2个脚本,一个是自动点击测试操作,另一个是记录测试时的系统、应用内存以及相关句柄的使用情况。
测试脚本:
#!/system/bin/sh
COUNT=1
FILE=/sdcard/top.txt
#input tap 800 500
#input text 1599828
#input tap 1600 500
echo "Auto test start:"
PROCESS=$1
if [ "$1" == "" ];then
echo "./auto_test.sh should with procecess name"
exit
fi
PID=`pidof $PROCESS`
echo "start test $PROCESS,pid is $PID:"
echo "======================================================="
while(true)
do
echo $COUNT : `date`
# input tap 1800 900
# input tap 800 500
#input text 1599828
#input tap 1600 500
#input keyevent 66
#swich to face login
#input tap 800 330
procrank | grep -E "$PROCESS|RAM"
cat /proc/meminfo | grep -A 2 MemTotal:
echo "------------------------------------------------------"
sleep 2
input tap 1000 700
sleep 6
input tap 1900 80
sleep 2
#confirm button ok
input tap 1150 750
if [ ! -d "/proc/$PID" ];then
TIME=$(date "+%Y%m%d_%H%M%S")
BUG_REPORT=/sdcard/bugreport_${TIME}_${PID}.txt
echo "$PROCESS is died at:" `date`
echo "save bugreport to:$BUG_REPORT"
bugreport > $BUG_REPORT
exit
fi
COUNT=$(expr $COUNT + 1 )
done;
记录脚本中的一部分如下:
#!/system/bin/sh
INTERVAL=60
ENABLE_LOG=true
PID=$1
TIME=$(date "+%Y%m%d-%H%M")
WORK_DIR=/sdcard/rk/$TIME
MEMINFO_DIR=$WORK_DIR/meminfo
LSOF_DIR=$WORK_DIR/lsof
LOG_DIR=$WORK_DIR/log
SYSTEM_MEM_DIR=$WORK_DIR/system_mem
STATUS_DIR=$WORK_DIR/status
THREAD_DIR=$WORK_DIR/thread
PROCRANK_DIR=$WORK_DIR/procrank
FD_DIR=$WORK_DIR/fd
PS=$WORK_DIR/ps.txt
LSOF=$WORK_DIR/lsof.txt
INFO=$WORK_DIR/info.txt
COUNT=1
mkdir -p $WORK_DIR
mkdir -p $MEMINFO_DIR
mkdir -p $LSOF_DIR
mkdir -p $LOG_DIR
mkdir -p $SYSTEM_MEM_DIR
mkdir -p $STATUS_DIR
mkdir -p $THREAD_DIR
mkdir -p $PROCRANK_DIR
mkdir -p $FD_DIR
#echo `date >> $LOG`
#echo `date >> $SLAB`
PROCESS_NAME=`cat /proc/$1/cmdline`
#set -x
if [ $1 ]; then
echo "================================================"
echo "Track process: $PROCESS_NAME,pid: $1 "
echo "Start at : `date`"
PID_EXIST=` ps | grep -w $PID | wc -l`
if [ $PID_EXIST -lt 1 ];then
echo "Pid :$1 not exsit!"
exit 1
fi
if [ ! -r /proc/$PID/fd ];then
echo "You should run in root user."
exit 2
fi
else
echo "You should run with track process pid!($0 pid)"
exit 4
fi
echo "Update logcat buffer size to 2M."
logcat -G 2M
echo "Save record in: $WORK_DIR"
echo Record start at:`date` >> $INFO
echo "$PROCESS_NAME,pid is:$1" >> $INFO
echo -------------------------------------------------->> $INFO
echo "Current system info:" >> $INFO
echo /proc/sys/kernel/threads-max: >> $INFO
cat /proc/sys/kernel/threads-max >> $INFO
echo /proc/$1/limits: >> $INFO
cat /proc/$1/limits >> $INFO
while((1));do
NOW=`date`
if [ ! -d "/proc/$PID" ];then
echo "$PROCESS_NAME is died,exit proc info record!"
echo -------------------------------------------------->> $INFO
echo "Record total $COUNT times." >> $INFO
logcat -d >> $LOG_DIR/last_log.txt
cp -rf /data/tombstones $WORK_DIR/
TIME=$(date "+%Y%m%d_%H%M%S")
BUG_REPORT=$WORK_DIR/bugreport_${TIME}_${PID}.txt
echo "save bugreport to:$BUG_REPORT"
bugreport > $BUG_REPORT
exit
fi
NUM=`ls -l /proc/$PID/fd | wc -l`
TIME_LABEL="\n$NOW:--------------------------------------------------:$COUNT"
echo -e $TIME_LABEL >> $PS
`ps >> $PS`
echo -e $TIME_LABEL >> $MEMINFO_DIR/${COUNT}_meminfo.txt
dumpsys meminfo $1 >> $MEMINFO_DIR/${COUNT}_meminfo.txt
echo -e $TIME_LABEL >> $SYSTEM_MEM_DIR/${COUNT}_sys_meminfo.txt
cat /proc/meminfo >> $SYSTEM_MEM_DIR/${COUNT}_sys_meminfo.txt
echo -e $TIME_LABEL >> $PROCRANK_DIR/${COUNT}_procrank.txt
procrank >> $PROCRANK_DIR/${COUNT}_procrank.txt
COUNT=$(expr $COUNT + 1 )
sleep $INTERVAL
done
测试大概2小时左右,问题复现。对比上面脚本记录下来的信息,未发现有句柄泄漏,cat /proc/meminfo显示系统可用内存并未明显减少。在对比ps的不同时间段结果时,有发现:
u0_a9 1070 217 2057984 379264 SyS_epoll_ b5fd37a4 S com.cnsg.card
u0_a9 1070 217 2663308 581404 binder_thr b5fd38e8 S com.cnsg.card
通过procrank抓取的对比,发现一段时间后,发现:
上面1070进程的Vss部分持续稳定地在增长。搜索的了相关资料:
可以确认,出现了虚拟内存泄漏。那下一步,就是确认这个进程,是哪个模块导致虚拟内存泄漏了。我们没有这个应用的代码,分析一时陷入死角。那继续从/proc/1070/中寻找突破中。
/proc/1070/status中,会记录进程的许多状态,如下:
***/proc/1070 # cat status
Name: com.cnsg.card
State: S (sleeping)
Tgid: 1070
Ngid: 0
Pid: 1070
PPid: 343
TracerPid: 0
Uid: 10062 10062 10062 10062
Gid: 10062 10062 10062 10062
FDSize: 128
Groups: 3003 9997 50062
VmPeak: 3619748 kB
VmSize: 3531764 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 878692 kB
VmRSS: 487580 kB
VmData: 1317136 kB
VmStk: 8192 kB
VmExe: 16 kB
VmLib: 177524 kB
VmPTE: 3488 kB
VmPMD: 32 kB
VmSwap: 0 kB
Threads: 48
SigQ: 0/15299
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000001204
SigIgn: 0000000000000000
SigCgt: 20000002000084f8
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000000000
CapAmb: 0000000000000000
Seccomp: 0
Cpus_allowed: 30
Cpus_allowed_list: 4-5
Mems_allowed: 1
Mems_allowed_list: 0
voluntary_ctxt_switches: 619990
nonvoluntary_ctxt_switches: 88065
上面的相关解释:
用户进程在/proc/{pid}/status文件中记录了该进程的内存使用实时情况。
* VmSize:
虚拟内存大小。
整个进程使用虚拟内存大小,是VmLib, VmExe, VmData, 和 VmStk的总和。
* VmLck:
虚拟内存锁。
进程当前使用的并且加锁的虚拟内存总数
* VmRSS:
虚拟内存驻留集合大小。
这是驻留在物理内存的一部分。它没有交换到硬盘。它包括代码,数据和栈。
* VmData:
虚拟内存数据。
堆使用的虚拟内存。
* VmStk:
虚拟内存栈
栈使用的虚拟内存
* VmExe:
可执行的虚拟内存
可执行的和静态链接库所使用的虚拟内存
* VmLib:
虚拟内存库
动态链接库所使用的虚拟内存
那就继续跑脚本,同时把这个进程的这个节点,也加入观察。运行一段时间后,发现除了Vm相关的变化,Threads也在持续增加,而且暂停测试,Threads也不会减少,好家伙,看来进程有持续创建进程,但没有销毁。进一步的,想知道是什么线程被持续创建呢,这边用到:pstree命令
busybox pstree 1070
com.cnsg.card-+-{Binder:15994_1}
|-{Binder:15994_2}
|-{Binder:15994_3}
|-{Binder:15994_4}
|-{Binder:15994_5}
|-{FinalizerDaemon}
|-{FinalizerWatchd}
|-{HeapTaskDaemon}
|-{JDWP}
|-{Jit thread pool}
|-{Profile Saver}
|-{ReferenceQueueD}
|-{RenderThread}
|-{Signal Catcher}
|-{Thread-11}
|-{Thread-12}
|-{Thread-15}
|-2*[{Thread-2}]
|-{Thread-4}
|-{Thread-5}
|-{Thread-6}
|-{Thread-7}
|-{Thread-8}
|-{Thread-9}
|-{hwuiTask1}
|-{hwuiTask2}
|-{mali-cmar-backe}
|-{mali-hist-dump}
|-{mali-mem-purge}
|-{RxCachedThreadS}(1927)
|-{RxCachedThreadS}(1928)
|-{RxCachedThreadS}(2140)
|-{RxCachedThreadS}(2141)
|-{RxCachedThreadS}(2289)
|-{RxCachedThreadS}(2290)
|-{RxCachedThreadS}(2458)
|-{RxCachedThreadS}(2464)
|-{RxCachedThreadS}(2465)
|-{RxCachedThreadS}(2614)
|-{RxCachedThreadS}(2615)
|-{RxCachedThreadS}(2792)
|-{RxCachedThreadS}(2793)
|-{RxCachedThreadS}(2958)
|-6*[{mali-utility-wo}]
|-10*[{myHandlerThread}]
`-{com.cnsg.card}
通过不同时段的对比,发现主要是RxCachedThreadS这类型线程在增加。到这里,原因就清楚了,进程中存在持续创建线程的代码,在创建时,都需要申请内存空间,包括虚拟和物理的,这边虚拟内存先被占满了,就出现了最前面log的错误。
本文地址:https://blog.csdn.net/cigogo/article/details/114321905
推荐阅读
-
Android 5.1 WebView内存泄漏问题及快速解决方法
-
分享Android平板电脑上开发应用程序不能全屏显示的问题解决
-
Android安装应用 INSTALL_FAILED_DEXOPT 问题及解决办法
-
Fedora14下android开发: eclipse与ibus确有冲突的问题分析
-
Android Studio 3.0上分析内存泄漏的原因
-
关于Yii2框架跑脚本时内存泄漏问题的分析与解决
-
解析android创建快捷方式会启动两个应用的问题
-
Android 静默方式实现批量安装卸载应用程序的深入分析
-
Android判断服务是否运行及定位问题实例分析
-
Android应用进程和线程问题解析