博客结构
用简单通俗的话来记录自己对架构的理解
1.背景
在一般的应用中,对前台进程和后台进程的内存要求不高。但是,现在的应用作为系统应用,在内置的时候,会考虑在内置进手机时,更何况现在是常驻进程时,这时的后台进程就显得弥足珍贵。
当然,以上是我们的业务需要,对于普通的应用。正如我们所知道,不同等级的进程的回收有个优先级,那么同一个优先级的,系统设置了一个阈值,当超过这个阈值的时候,就会被回收(各个等级进程的阈值如何查看:)。正如我们所知,我们更多期望我们的进程能更长时间在后台。
那么,要保证进程后台进程内存和峰值内存,就要了解内存的优化。如何优化内存呢?这是下一步,那么第一步就是要发现可优化的内存有哪些?我们的内存是有哪些内存占用衡量。
2.内存查看方式
(1).AS-Profiler
AS-Profiler虽然不如MAT的功能那么强大,但是,因为是AS自带,易于方便查看。
(1).运行框-命令行
1.相关命令
(1)查看系统中各个进程内存信息
adb shell dumpsys meminfo ;
(2)查看系统指定进程内存信息
adb shell dumsys meminfo pid;
2.原理:
Meminfo是最容易获取的一种查看内存数据的方式,底层实现是根据smaps、graphics等相关内存进行汇总分类而形成的。里面提供的数据都是PSS,单位KB。
该命令执行前会自动执行一次强制GC,#System.gc/Runtime.getRuntime().gc()#,然后再计算内存。由于GC是异步,所以第一次执行得到的meminfo不一定是完成GC后的内存,最后是两次dump来获取GC的内存,以排除GC部分的干扰
3.内存参数
Profiler的详细解析:
https://developer.android.com/studio/profile/memory-profiler?hl=zh-cn
(0)PSS
实际使用物理内存(比例分配共享库占用的内存)
对于共享库,可以参考Linux共享库、静态库、动态库详解。
https://www.cnblogs.com/sunsky303/p/7731911.html
而共享库是一个可以执行程序在启动时被加载。
(1)Java heap
从Java/Kotlin代码分配的对象的内存
(2)Native Heap
从C/C++代码分配的内存
(3)Code
用于处理代码和资源(如dex字节码,经过优化/编译的dex代码、so库和字体)的内存
(4)Stack
应用中原生堆栈和Java堆栈使用的内存。由于Java语言天生的多线程语言。这通常与应用运行多少线程有关。
(5)Graphics
图形缓冲区对垒向屏幕显示像素(包括GL表面,GL纹理等)所使用的内存。
(6)Private Other
应用所使用的系统不确定如何分类的内存
(7)System
(8)其它参数
(1)对于AS-Profiler
(a)Allocated
Java/Kotlin的对象数
(2)对于运行库的命令行
(a)Native Heap: Native代码分配的内存(比如so库中分配的内存)、虚拟机、Framework分配的内存
(b)Dalvik Heap: Java对象分配的占据内存
©Dalvik Other:类数据结构和索引占据内存(数组在这里面吗???)
(d)Stack: 应用中的原生堆栈(线程分配)和Java堆栈使用的内存。这通常和应用运行多少线程有关
(e)Ashmem:不以dalvik-开头的内存区域,匿名共享内存用来提供共享内存通过分配一个多个进程可以共享的带名称的内存块。Android匿名共享内存是基于Linux共享内存的,都是tmpfs文件系统上新建文件,并将其映射到不同的进程空间,从而达到共享内存的目的,只是Android在Linux的基础上进行了改造,并借助Binder+fd文件描述符实现了共享内存的传递
(f)other dev:内部driver占用的内存
(g).so mmap:C库代码占用内存
(h).jar mmap:java文件代码占用的内存
(i).apk mmap:apk代码占用的内存
(k).ttf mmap:ttf文件代码占用的内存
(l).dex mmap:dex文件占用内存
(m)other mmap :其它文件占用的内存
(n)EGL mtrack:ION内存
(o)GL mtrack:GPU内存
(q)clean和dirty
Clean和Dirty之间的区别是指自从写入内存以来页面是否已写出到后备存储
(d)不同维度统计的关系
Java heap=Dalvik private dirty+.art mmap private clean+.art mmap private dirty=8461+624+164 =9204
native heap = native private dirty=11148
code = .so private (clean+dirty) + .jar private (clean+dirty) + .apk private (clean+dirty) + .ttf private (clean+ dirty) + .dex private (clean + dirty) + .oat private (clean + dirty)=152+144+772+184+4476+228=5956
stack = stack private dirty = 36
graphics= GL mtrack private (clean + dirty) + EGL mtrack private(clean + dirty)=6701+15773=22474
private other= private clean列+private dirty列-java heap-native heap-code-stack-graphic=46922+7868-9204-11148-5956-36-22474=5972
system=total-private Clean 列总和+private dirty列总和=131825-46922-7868=77035
4.异常分析示例
(1).apk mmap过高:代码有扫描APK行为,使用了类似ApplicationInfo.loadLabel函数。这一块的内存主要是APK文件映射到进程地址空间引用的,此项增长也会同步导致Native Heap 内存增长。
(2)Activities的数量只增不减或者总体上升:Activity泄漏
(3)如果发现是mmap有异常,继续分析smaps文件。/proc//smaps文件主要记录进程内存映射信息。一般先转化成showmap文件更直观
(4)Bitmap的占用内存过大
(5)Native内存问题分析
依据:malloc_debug,是android默认集成的native内存调试工具。设计思路是对lib.so库添加一层wrapper,在wrapper中添加一些记录数据的队列等。把每次申请的内存以指针为索引记录添加到队列中,释放时从队列中移除该记录。当出现可疑内存泄漏时,打印出该队列中的记录,然后结合代码检查这些记录是否存在内存泄漏。
使用这种方式,一定是root的手机,所以,这种方式可能已经过滤掉了很多人了。再加上实际上现在我还没有调试通
adb shell
ABC:/ # setprop libc.debug.malloc.program netd // 或者其他native进程名
ABC:/ # setprop libc.debug.malloc.options "backtrace_enable_on_signal leak_track backtrace=8" // backtace 设置为8,减少debug带来的卡顿
ABC:/ # kill -9 <pid>
ABC:/ # cat /proc/<pid>/smaps | grep malloc_debug // 查看是否有libc_malloc_debug.so,有的话则确认debug生效,无则需要查看是否操作有误
ABC:/ # kill -45 <pid> // 开始调试//接下来是复现问题,或者操作场景
ABC:/ # kill -47 <pid> //停止调试并dump backtrace
kill - 47 默认存储再data/local/temp目录下,有可能存在debug进程无法使用该目录的情况,可以使用
am dumpheap -n <pid>/data/local/tmp/temp.txt获取backtrace。
获取到backtrace后,还需要再使用脚本文件对数backtrace解析
5.站在巨人肩膀上
https://www.jianshu.com/p/0df5ad0d2e6a
https://www.zhihu.com/question/29833675
https://blog.csdn.net/fox_bert/article/details/88367002
https://www.cnblogs.com/ldq2016/p/8469836.html
1.smaps
linux通过proc文件系统为每个进程提供一个smaps文件。
https://blog.csdn.net/tangtang_yue/article/details/78298067