欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android应用虚拟内存泄漏问题分析

程序员文章站 2022-06-16 09:16:27
Android应用虚拟内存泄漏问题分析android系统上,各种订制修改比较多,经常会遇到一些奇怪的内存泄漏问题。最近遇到一个比较少见的应用端泄漏,这边记录一下。首先,应用运行大概2小时左右,遇到如下崩溃:02-04 13:15:03.661 1070 1087 W libc : pthread_create failed: couldn't allocate 1069056-bytes mapped space: Out of memory02-04 13:15:03.661...

android系统上,各种订制修改比较多,经常会遇到一些奇怪的内存泄漏问题。最近遇到一个比较少见的应用端泄漏,这边记录一下。

最开始,就是使用Android Studio自带的利器:Profiler

Android应用虚拟内存泄漏问题分析

 

 

 

应用运行大概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