当前位置:   article > 正文

Android应用虚拟内存泄漏问题分析_encounter the first mali_error : 0x0002 : failed t

encounter the first mali_error : 0x0002 : failed to allocate cpu memory

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

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

 

 

 

应用运行大概2小时左右,抓取到的图如上,没发现有明显的内存增加,但遇到如下崩溃:

  1. 02-04 13:15:03.661 1070 1087 W libc : pthread_create failed: couldn't allocate 1069056-bytes mapped space: Out of memory
  2. 02-04 13:15:03.661 1070 1087 W art : Throwing OutOfMemoryError "pthread_create (1040KB stack) failed: Try again"
  3. 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)
  4. 02-04 13:15:18.664 1070 1627 E OpenGLRenderer: GL error: Out of memory!
  5. 02-04 13:15:18.665 1070 1627 F OpenGLRenderer: GL errors! frameworks/base/libs/hwui/renderthread/CanvasContext.cpp:550

不到1M的内存都分配不出来了?通过下面命令,看起来系统还是有500多M空闲内存。

  1. cat /proc/meminfo
  2. MemTotal: 2045160 kB
  3. MemFree: 529064 kB
  4. MemAvailable: 1250020 kB
  5. Buffers: 1300 kB
  6. Cached: 891916 kB
  7. SwapCached: 0 kB
  8. Active: 556296 kB
  9. Inactive: 674200 kB
  10. Active(anon): 235800 kB
  11. Inactive(anon): 224668 kB
  12. Active(file): 320496 kB
  13. Inactive(file): 449532 kB
  14. Unevictable: 256 kB
  15. Mlocked: 256 kB

那就比较奇怪了,这边写了2个脚本,一个是自动点击测试操作,另一个是记录测试时的系统、应用内存以及相关句柄的使用情况。

测试脚本:

  1. #!/system/bin/sh
  2. COUNT=1
  3. FILE=/sdcard/top.txt
  4. #input tap 800 500
  5. #input text 1599828
  6. #input tap 1600 500
  7. echo "Auto test start:"
  8. PROCESS=$1
  9. if [ "$1" == "" ];then
  10. echo "./auto_test.sh should with procecess name"
  11. exit
  12. fi
  13. PID=`pidof $PROCESS`
  14. echo "start test $PROCESS,pid is $PID:"
  15. echo "======================================================="
  16. while(true)
  17. do
  18. echo $COUNT : `date`
  19. # input tap 1800 900
  20. # input tap 800 500
  21. #input text 1599828
  22. #input tap 1600 500
  23. #input keyevent 66
  24. #swich to face login
  25. #input tap 800 330
  26. procrank | grep -E "$PROCESS|RAM"
  27. cat /proc/meminfo | grep -A 2 MemTotal:
  28. echo "------------------------------------------------------"
  29. sleep 2
  30. input tap 1000 700
  31. sleep 6
  32. input tap 1900 80
  33. sleep 2
  34. #confirm button ok
  35. input tap 1150 750
  36. if [ ! -d "/proc/$PID" ];then
  37. TIME=$(date "+%Y%m%d_%H%M%S")
  38. BUG_REPORT=/sdcard/bugreport_${TIME}_${PID}.txt
  39. echo "$PROCESS is died at:" `date`
  40. echo "save bugreport to:$BUG_REPORT"
  41. bugreport > $BUG_REPORT
  42. exit
  43. fi
  44. COUNT=$(expr $COUNT + 1 )
  45. done;

记录脚本中的一部分如下:

  1. #!/system/bin/sh
  2. INTERVAL=60
  3. ENABLE_LOG=true
  4. PID=$1
  5. TIME=$(date "+%Y%m%d-%H%M")
  6. WORK_DIR=/sdcard/rk/$TIME
  7. MEMINFO_DIR=$WORK_DIR/meminfo
  8. LSOF_DIR=$WORK_DIR/lsof
  9. LOG_DIR=$WORK_DIR/log
  10. SYSTEM_MEM_DIR=$WORK_DIR/system_mem
  11. STATUS_DIR=$WORK_DIR/status
  12. THREAD_DIR=$WORK_DIR/thread
  13. PROCRANK_DIR=$WORK_DIR/procrank
  14. FD_DIR=$WORK_DIR/fd
  15. PS=$WORK_DIR/ps.txt
  16. LSOF=$WORK_DIR/lsof.txt
  17. INFO=$WORK_DIR/info.txt
  18. COUNT=1
  19. mkdir -p $WORK_DIR
  20. mkdir -p $MEMINFO_DIR
  21. mkdir -p $LSOF_DIR
  22. mkdir -p $LOG_DIR
  23. mkdir -p $SYSTEM_MEM_DIR
  24. mkdir -p $STATUS_DIR
  25. mkdir -p $THREAD_DIR
  26. mkdir -p $PROCRANK_DIR
  27. mkdir -p $FD_DIR
  28. #echo `date >> $LOG`
  29. #echo `date >> $SLAB`
  30. PROCESS_NAME=`cat /proc/$1/cmdline`
  31. #set -x
  32. if [ $1 ]; then
  33. echo "================================================"
  34. echo "Track process: $PROCESS_NAME,pid: $1 "
  35. echo "Start at : `date`"
  36. PID_EXIST=` ps | grep -w $PID | wc -l`
  37. if [ $PID_EXIST -lt 1 ];then
  38. echo "Pid :$1 not exsit!"
  39. exit 1
  40. fi
  41. if [ ! -r /proc/$PID/fd ];then
  42. echo "You should run in root user."
  43. exit 2
  44. fi
  45. else
  46. echo "You should run with track process pid!($0 pid)"
  47. exit 4
  48. fi
  49. echo "Update logcat buffer size to 2M."
  50. logcat -G 2M
  51. echo "Save record in: $WORK_DIR"
  52. echo Record start at:`date` >> $INFO
  53. echo "$PROCESS_NAME,pid is:$1" >> $INFO
  54. echo -------------------------------------------------->> $INFO
  55. echo "Current system info:" >> $INFO
  56. echo /proc/sys/kernel/threads-max: >> $INFO
  57. cat /proc/sys/kernel/threads-max >> $INFO
  58. echo /proc/$1/limits: >> $INFO
  59. cat /proc/$1/limits >> $INFO
  60. while((1));do
  61. NOW=`date`
  62. if [ ! -d "/proc/$PID" ];then
  63. echo "$PROCESS_NAME is died,exit proc info record!"
  64. echo -------------------------------------------------->> $INFO
  65. echo "Record total $COUNT times." >> $INFO
  66. logcat -d >> $LOG_DIR/last_log.txt
  67. cp -rf /data/tombstones $WORK_DIR/
  68. TIME=$(date "+%Y%m%d_%H%M%S")
  69. BUG_REPORT=$WORK_DIR/bugreport_${TIME}_${PID}.txt
  70. echo "save bugreport to:$BUG_REPORT"
  71. bugreport > $BUG_REPORT
  72. exit
  73. fi
  74. NUM=`ls -l /proc/$PID/fd | wc -l`
  75. TIME_LABEL="\n$NOW:--------------------------------------------------:$COUNT"
  76. echo -e $TIME_LABEL >> $PS
  77. `ps >> $PS`
  78. echo -e $TIME_LABEL >> $MEMINFO_DIR/${COUNT}_meminfo.txt
  79. dumpsys meminfo $1 >> $MEMINFO_DIR/${COUNT}_meminfo.txt
  80. echo -e $TIME_LABEL >> $SYSTEM_MEM_DIR/${COUNT}_sys_meminfo.txt
  81. cat /proc/meminfo >> $SYSTEM_MEM_DIR/${COUNT}_sys_meminfo.txt
  82. echo -e $TIME_LABEL >> $PROCRANK_DIR/${COUNT}_procrank.txt
  83. procrank >> $PROCRANK_DIR/${COUNT}_procrank.txt
  84. COUNT=$(expr $COUNT + 1 )
  85. sleep $INTERVAL
  86. done

测试大概2小时左右,问题复现。对比上面脚本记录下来的信息,未发现有句柄泄漏,cat /proc/meminfo显示系统可用内存并未明显减少。在对比ps的不同时间段结果时,有发现:

  1. u0_a9 1070 217 2057984 379264 SyS_epoll_ b5fd37a4 S com.cnsg.card
  2. 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中,会记录进程的许多状态,如下:

  1. ***/proc/1070 # cat status
  2. Name: com.cnsg.card
  3. State: S (sleeping)
  4. Tgid: 1070
  5. Ngid: 0
  6. Pid: 1070
  7. PPid: 343
  8. TracerPid: 0
  9. Uid: 10062 10062 10062 10062
  10. Gid: 10062 10062 10062 10062
  11. FDSize: 128
  12. Groups: 3003 9997 50062
  13. VmPeak: 3619748 kB
  14. VmSize: 3531764 kB
  15. VmLck: 0 kB
  16. VmPin: 0 kB
  17. VmHWM: 878692 kB
  18. VmRSS: 487580 kB
  19. VmData: 1317136 kB
  20. VmStk: 8192 kB
  21. VmExe: 16 kB
  22. VmLib: 177524 kB
  23. VmPTE: 3488 kB
  24. VmPMD: 32 kB
  25. VmSwap: 0 kB
  26. Threads: 48
  27. SigQ: 0/15299
  28. SigPnd: 0000000000000000
  29. ShdPnd: 0000000000000000
  30. SigBlk: 0000000000001204
  31. SigIgn: 0000000000000000
  32. SigCgt: 20000002000084f8
  33. CapInh: 0000000000000000
  34. CapPrm: 0000000000000000
  35. CapEff: 0000000000000000
  36. CapBnd: 0000000000000000
  37. CapAmb: 0000000000000000
  38. Seccomp: 0
  39. Cpus_allowed: 30
  40. Cpus_allowed_list: 4-5
  41. Mems_allowed: 1
  42. Mems_allowed_list: 0
  43. voluntary_ctxt_switches: 619990
  44. nonvoluntary_ctxt_switches: 88065

上面的相关解释:

  1. 用户进程在/proc/{pid}/status文件中记录了该进程的内存使用实时情况。
  2. * VmSize:
  3. 虚拟内存大小。
  4. 整个进程使用虚拟内存大小,是VmLib, VmExe, VmData, 和 VmStk的总和。
  5. * VmLck:
  6. 虚拟内存锁。
  7. 进程当前使用的并且加锁的虚拟内存总数
  8. * VmRSS:
  9. 虚拟内存驻留集合大小。
  10. 这是驻留在物理内存的一部分。它没有交换到硬盘。它包括代码,数据和栈。
  11. * VmData:
  12. 虚拟内存数据。
  13. 堆使用的虚拟内存。
  14. * VmStk:
  15. 虚拟内存栈
  16. 栈使用的虚拟内存
  17. * VmExe:
  18. 可执行的虚拟内存
  19. 可执行的和静态链接库所使用的虚拟内存
  20. * VmLib:
  21. 虚拟内存库
  22. 动态链接库所使用的虚拟内存

那就继续跑脚本,同时把这个进程的这个节点,也加入观察。运行一段时间后,发现除了Vm相关的变化,Threads也在持续增加,而且暂停测试,Threads也不会减少,好家伙,看来进程有持续创建进程,但没有销毁。进一步的,想知道是什么线程被持续创建呢,这边用到:pstree命令

  1. busybox pstree 1070
  2. com.cnsg.card-+-{Binder:15994_1}
  3. |-{Binder:15994_2}
  4. |-{Binder:15994_3}
  5. |-{Binder:15994_4}
  6. |-{Binder:15994_5}
  7. |-{FinalizerDaemon}
  8. |-{FinalizerWatchd}
  9. |-{HeapTaskDaemon}
  10. |-{JDWP}
  11. |-{Jit thread pool}
  12. |-{Profile Saver}
  13. |-{ReferenceQueueD}
  14. |-{RenderThread}
  15. |-{Signal Catcher}
  16. |-{Thread-11}
  17. |-{Thread-12}
  18. |-{Thread-15}
  19. |-2*[{Thread-2}]
  20. |-{Thread-4}
  21. |-{Thread-5}
  22. |-{Thread-6}
  23. |-{Thread-7}
  24. |-{Thread-8}
  25. |-{Thread-9}
  26. |-{hwuiTask1}
  27. |-{hwuiTask2}
  28. |-{mali-cmar-backe}
  29. |-{mali-hist-dump}
  30. |-{mali-mem-purge}
  31. |-{RxCachedThreadS}(1927)
  32. |-{RxCachedThreadS}(1928)
  33. |-{RxCachedThreadS}(2140)
  34. |-{RxCachedThreadS}(2141)
  35. |-{RxCachedThreadS}(2289)
  36. |-{RxCachedThreadS}(2290)
  37. |-{RxCachedThreadS}(2458)
  38. |-{RxCachedThreadS}(2464)
  39. |-{RxCachedThreadS}(2465)
  40. |-{RxCachedThreadS}(2614)
  41. |-{RxCachedThreadS}(2615)
  42. |-{RxCachedThreadS}(2792)
  43. |-{RxCachedThreadS}(2793)
  44. |-{RxCachedThreadS}(2958)
  45. |-6*[{mali-utility-wo}]
  46. |-10*[{myHandlerThread}]
  47. `-{com.cnsg.card}

通过不同时段的对比,发现主要是RxCachedThreadS这类型线程在增加。到这里,原因就清楚了,进程中存在持续创建线程的代码,在创建时,都需要申请内存空间,包括虚拟和物理的,这边虚拟内存先被占满了,就出现了最前面log的错误。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/292559
推荐阅读
相关标签
  

闽ICP备14008679号