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部分持续稳定地在增长。搜索的了相关资料:
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
上面的相关解释:
用户进程在/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的错误。
推荐阅读
-
解决Android平台上Assimp库的内存泄漏问题
-
搞定App内存问题:一步步分析和优化内存泄漏的方法
-
Android应用虚拟内存泄漏问题分析
-
【2022新手指南】Java编程进阶之路 - 六、技术架构篇 ### MySQL索引底层解析与优化实战 - 你会讲解MySQL索引的数据结构吗?性能调优技巧知多少? - Redis深度揭秘:你知道多少?从基础到哨兵、主从复制全梳理 - Redis持久化及哨兵模式详解,还有集群搭建和Leader选举黑箱打开 - Zookeeper是个啥?特性和应用场景大公开 - ZooKeeper集群搭建攻略及 Leader选举、读写一致性、共享锁实现细节 - 探究ZooKeeper中的Leader选举机制及其在分布式环境中的作用 - Zab协议深入剖析:原理、功能与在Zookeeper中的核心地位 - RabbitMQ全方位解读:工作模式、消费限流、可靠投递与配置策略 - 设计者视角:RabbitMQ过期时间、死信队列与延时队列实践指南 - RocketMQ特性和应用场景揭示:理解其精髓与差异化优势 - Kafka详细介绍:特性及广泛应用于实时数据处理的场景解析 - ElasticSearch实力揭秘:特性概述与作为搜索引擎的广泛应用 - MongoDB认知升级:非关系型数据库的优势阐述,安装与使用实战教学 - BIO/NIO/AIO网络模型对比:掌握它们的区别与在网络编程中的实际应用 - Netty带你飞:理解其超快速度背后的秘密,包括线程模型分析 - 网络通信黑科技:Netty编解码原理与常用编解码器的应用,Protostuff实战演示 - 解密Netty粘包与拆包现象,怎样有效应对这一常见问题 - 自定义Netty心跳检测机制,轻松调整检测间隔时间的艺术 - Dubbo轻骑兵介绍:核心特性概览,服务降级实战与其实现益处 - Dubbo三大神器解读:本地存根与本地伪装的实战运用与优势呈现 ----------------------- 七、结语与回顾
-
Android应用开发中常遇到的问题解答
-
必备技巧:掌握图片压缩秘诀,轻松解决 Android 应用程序内存溢出问题
-
深入解析Android性能测试:专讲内存表现与内存泄漏问题
-
Android O 8.0,完美解决版本升级不跳转到应用安装页面的问题
-
android 支持包 com.android.support:support-v4 依赖关系树,便于分析类冲突问题
-
包婷婷 (201550484)作业一 统计软件简介与数据操作-SPSS(Statistical Product and Service Solutions),"统计产品与服务解决方案"软件。最初软件全称为"(SolutionsStatistical Package for the Social Sciences),但是随着SPSS产品服务领域的扩大和服务深度的增加,SPSS公司已于2000年正式将英文全称更改为"统计产品与服务解决方案",标志着SPSS的战略方向正在做出重大调整。为IBM公司推出的一系列用于统计学分析运算、数据挖掘、预测分析和决策支持任务的软件产品及相关服务的总称SPSS,有Windows和Mac OS X等版本。 1984年SPSS总部首先推出了世界上第一个统计分析软件微机版本SPSS/PC+,开创了SPSS微机系列产品的开发方向,极大地扩充了它的应用范围,并使其能很快地应用于自然科学、技术科学、社会科学的各个领域。世界上许多有影响的报刊杂志纷纷就SPSS的自动统计绘图、数据的深入分析、使用方便、功能齐全等方面给予了高度的评价。 R统计软件介绍 R是一套完整的数据处理、计算和制图软件系统。其功能包括:数据存储和处理系统;数组运算工具(其向量、矩阵运算方面功能尤其强大);完整连贯的统计分析工具;优秀的统计制图功能;简便而强大的编程语言:可操纵数据的输入和输出,可实现分支、循环,用户可自定义功能。 与其说R是一种统计软件,还不如说R是一种数学计算的环境,因为R并不是仅仅提供若干统计程序、使用者只需指定数据库和若干参数便可进行一个统计分析。R的思想是:它可以提供一些集成的统计工具,但更大量的是它提供各种数学计算、统计计算的函数,从而使使用者能灵活机动的进行数据分析,甚至创造出符合需要的新的统计计算方法。 该语言的语法表面上类似 C,但在语义上是函数设计语言(functional programming language)的变种并且和Lisp 以及 APL有很强的兼容性。特别的是,它允许在"语言上计算"(computing on the language)。这使得它可以把表达式作为函数的输入参数,而这种做法对统计模拟和绘图非常有用。 R是一个免费的*软件,它有UNIX、LINUX、MacOS和WINDOWS版本,都是可以免费下载和使用的。在R主页那儿可以下载到R的安装程序、各种外挂程序和文档。在R的安装程序中只包含了8个基础模块,其他外在模块可以通过CRAN获得。 二、R语言 R是用于统计分析、绘图的语言和操作环境。R是属于GNU系统的一个*、免费、源代码开放的软件,它是一个用于统计计算和统计制图的优秀工具。 R作为一种统计分析软件,是集统计分析与图形显示于一体的。它可以运行于UNIX,Windows和Macintosh的操作系统上,而且嵌入了一个非常方便实用的帮助系统,相比于其他统计分析软件,R还有以下特点: 1.R是*软件。这意味着它是完全免费,开放源代码的。可以在它的网站及其镜像中下载任何有关的安装程序、源代码、程序包及其源代码、文档资料。标准的安装文件身自身就带有许多模块和内嵌统计函数,安装好后可以直接实现许多常用的统计功能。[2] 2.R是一种可编程的语言。作为一个开放的统计编程环境,语法通俗易懂,很容易学会和掌握语言的语法。而且学会之后,我们可以编制自己的函数来扩展现有的语言。这也就是为什么它的更新速度比一般统计软件,如,SPSS,SAS等快得多。大多数最新的统计方法和技术都可以在R中直接得到。[2] 3. 所有R的函数和数据集是保存在程序包里面的。只有当一个包被载入时,它的内容才可以被访问。一些常用、基本的程序包已经被收入了标准安装文件中,随着新的统计分析方法的出现,标准安装文件中所包含的程序包也随着版本的更新而不断变化。在另外版安装文件中,已经包含的程序包有:base一R的基础模块、mle一极大似然估计模块、ts一时间序列分析模块、mva一多元统计分析模块、survival一生存分析模块等等.[2] 4.R具有很强的互动性。除了图形输出是在另外的窗口处,它的输入输出窗口都是在同一个窗口进行的,输入语法中如果出现错误会马上在窗口口中得到提示,对以前输入过的命令有记忆功能,可以随时再现、编辑修改以满足用户的需要。输出的图形可以直接保存为JPG,BMP,PNG等图片格式,还可以直接保存为PDF文件。另外,和其他编程语言和数据库之间有很好的接口。[2] 5.如果加入R的帮助邮件列表一,每天都可能会收到几十份关于R的邮件资讯。可以和全球一流的统计计算方面的专家讨论各种问题,可以说是全世界最大、最前沿的统计学家思维的聚集地.[2] R是基于S语言的一个GNU项目,所以也可以当作S语言的一种实现,通常用S语言编写的代码都可以不作修改的在R环境下运行。 R的语法是来自Scheme。R的使用与S-PLUS有很多类似之处,这两种语言有一定的兼容性。S-PLUS的使用手册,只要稍加修改就可作为R的使用手册。所以有人说:R,是S-PLUS的一个“克隆”。 但是请不要忘了:R是免费的(R is free)。R语言源代码托管在github,具体地址可以看参考资料。[3] 。 R语言的下载可以通过CRAN的镜像来查找。 R语言有域名为.cn的下载地址,有六个,其中两个由Datagurn,由 中国科学技术大学提供的。R语言Windows版,其中由两个下载地点是Datagurn和 USTC提供的。 三、stata Stata 是一套提供其使用者数据分析、数据管理以及绘制专业图表的完整及整合性统计软件。它提供许许多多功能,包含线性混合模型、均衡重复反复及多项式普罗比模式。用Stata绘制的统计图形相当精美。 新版本的STATA采用最具亲和力的窗口接口,使用者自行建立程序时,软件能提供具有直接命令式的语法。Stata提供完整的使用手册,包含统计样本建立、解释、模型与语法、文献等超过一万余页的出版品。 除此之外,Stata软件可以透过网络实时更新每天的最新功能,更可以得知世界各地的使用者对于STATA公司提出的问题与解决之道。使用者也可以透过Stata. Journal获得许许多多的相关讯息以及书籍介绍等。另外一个获取庞大资源的管道就是Statalist,它是一个独立的listserver,每月交替提供使用者超过1000个讯息以及50个程序。 四、PYTHON