赞
踩
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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
那就比较奇怪了,这边写了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;
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
记录脚本中的一部分如下:
- #!/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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
测试大概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部分持续稳定地在增长。搜索的了相关资料:
VmRSS不能判定内存泄漏, VmSize才可以
典型的内存泄漏往往会使VmSize和VmRSS同时增长,内存泄漏通过观察VmRSS就能发现(也可能发现不了, 比如只malloc不使用);但并不是说VmRSS增长了就有内存泄漏。
其实对VmSize的监控才是合理的,原因如下:
VmSize是进程所有的内存(文件映射,共享内存,堆,任何其它的内存的总和,它包含VmRSS),它的变化并不是想象的“非常快”,ch___mgr就长期稳定在58824 K上,因为没有不匹配的malloc/free,VmSize不会涨上去
VmRSS是实际用到的物理内存,由于业务的需要它增长变化是合理的,就比如此例中的ch___mgr
打个比方,VmSize是一个官员拥有多少资产(固定资产、存银行的钱、放家里的钱、等等所有资产的总和)
VmRSS 是这官员人放在家里的钱
我们现在监控这个官员有没有贪污钱财,应该监控他所有的资产VmSize有没有增加,而不是监控放在家里的钱VmRSS
如果官员从银行里面把钱取出来放在家里就认定为贪污,显然很荒唐
可以确认,出现了虚拟内存泄漏。那下一步,就是确认这个进程,是哪个模块导致虚拟内存泄漏了。我们没有这个应用的代码,分析一时陷入死角。那继续从/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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
上面的相关解释:
- 用户进程在/proc/{pid}/status文件中记录了该进程的内存使用实时情况。
- * VmSize:
- 虚拟内存大小。
- 整个进程使用虚拟内存大小,是VmLib, VmExe, VmData, 和 VmStk的总和。
- * VmLck:
- 虚拟内存锁。
- 进程当前使用的并且加锁的虚拟内存总数
- * VmRSS:
- 虚拟内存驻留集合大小。
- 这是驻留在物理内存的一部分。它没有交换到硬盘。它包括代码,数据和栈。
- * VmData:
- 虚拟内存数据。
- 堆使用的虚拟内存。
- * VmStk:
- 虚拟内存栈
- 栈使用的虚拟内存
- * VmExe:
- 可执行的虚拟内存
- 可执行的和静态链接库所使用的虚拟内存
- * VmLib:
- 虚拟内存库
- 动态链接库所使用的虚拟内存
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
那就继续跑脚本,同时把这个进程的这个节点,也加入观察。运行一段时间后,发现除了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}
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
通过不同时段的对比,发现主要是RxCachedThreadS这类型线程在增加。到这里,原因就清楚了,进程中存在持续创建线程的代码,在创建时,都需要申请内存空间,包括虚拟和物理的,这边虚拟内存先被占满了,就出现了最前面log的错误。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。