1. # dumpsys meminfo <pid> 查看某个进程的内存,然后将正常的和不正常的进行对比来找排查方向。
2. 进程分类内存: PssTotal列 + SwapPssDirty列
3. native中的一些内存会算在 Unknown 里面
4. 分析 HPPROF 文件,需要先使用 hprof-conv.exe source.hprof target.hprof,然后使用MAT打开对象列表: 运行 MAT -> File -> Open Heap Dump... -> 点击Histogram,打开对象列表。
注意:
a. 文件后缀必须为.hprof后缀。
b. Android Profile可以查看app/image/zygote的内存分配,建议两个工具一起使用。
c. Android Profile不需要转化,可直接打开hprof
5. Java Heap 泄漏的本质是生命周期长的对象持有生命周期短的对象导致无法回收而导致的泄漏。
6. App发生OOM需要分析hprof文件。
7. 分析工具资源链接
MAT:http://wiki.eclipse.org/MemoryAnalyer Android Profile: https://developer.android.com/studio/profile/memory-profiler //短链接路径资料更多 LeakCanary: https://square.github.io/leakcanary/ malloc_debug: https://android.googlesource.com/platform/bionic/+/master/libc/malloc_debug/ //先看README GWP-ASan: https://developer.android.google.cn/ndk/guides/gwp-asan
1. KASAN 工具介绍
KASAN(kernel Address Sanitizer)是一个动态内存检查工具,可以实时监测 out-of-bound访问(slab/stack/globals) 和 use-after-free(slab/stack)。实现原理是依靠编译阶段插桩和影子内存技术,实现对一块内存的监测,并在监测到异常时实时记录异常现场。
工具优点:实时监测内存异常,可以抓到现场,并提供详细信息(进程、寄存器内容、调用栈、异常类型)。
工具缺点:会消耗大量内存,影子内存消耗1/8的物理内存; Kernel镜像大小会增加,因为会在全局变量、局部变量前后插入魔术字以及代码段插桩;会对运行性能有严重影响。
KASAN详细介绍和使用方法参考:https://www.kernel.org/doc/html/latest/dev-tools/kasan.html
2. KASAN+HWASAN 工具介绍
HWASAN(Hardware-assisted Address Sanitize)类似于KASAN的升级版,基于Arm64架构支持的Memory tagging实现的。Arm64架构支持设置一段内存的tag,类似对这段内存着色,在访问已经着色的内存时校验tag信息是否有效。这些操作可以通过插入汇编指令来完成。
工具优点:相对于KASAN,使用更少的内存,大概只增加3%-5%; 对性能更友好,负载在10%以下。
工具缺点:由于bit个数限制,存在遗漏问题的现象,正常情况下遗漏概率8bit是1/256=0.4%,4bit是1/16=16.3%; 只支持Arm64架构。
KASAN+HWASAN详细介绍和使用方法参考:https://source.android.com/devices/tech/debug/hwasan
3. kmemleak 工具介绍
kmemleak是用来监测 slab/vmalloc/alloc_bootmem/pcpu_alloc 等函数分配内存存在泄漏的问题,主要在vmalloc/vfree、kmalloc/kfree、kmem_cach_alloc/kmem_cache_free、early memory alloc(boot阶段使用bootmem)函数插桩,当分配成功时 kmemleak 对象记录分配内存信息,通过红黑树链表管理。周期扫描所有task的内核栈以及内核数据段内存内容来查找内存泄漏。目前已经支持多个平台。
工具优点:很准确监测到内存泄漏比较大的场景; 提供十分详细的debug信息。
工具缺点:消耗大量的内存; 影响性能; 对于小内存泄漏存在误报; 因此只能在实验室中使用。
kmemleak详细介绍和使用方法参考:https://www.kernel.org/doc/html/latest/dev-tools/kasan.html
4. SLUB_DEBUG 工具介绍
SLUB_DEBUG是内核原生slub维测工具,可以监测slub的越界访问、访问已释放内存、重复释放、内存泄漏。SLUB_DEBUG与kasan有部分功能重叠,但监测时机和方法有一定差异。
工具优点:支持异常监测的种类多,功能强大; 可以根据维测需求定制。
工具缺点:部分功能消耗大量内存; 执行流程变长影响性能(SLAB_POISON--object内容、SLAB_RED_ZONE--Readzone && read_left_pad、SLAB_STORE_USER--track、SLAB_CONSISTENCY_CHECKS、SLAB_TRACE)
SLUB_DEBUG详细介绍和使用方法参考:https://www.kernel.org/doc/html/latest/dev-tools/kasan.html
5. page_owner 工具介绍
page_owner是内核原生功能,通过记录每次分配page的进程和调用栈信息,来定位物理内存泄漏。每个page都有page_owner数据结构,用于记录page的使用信息(order、gfp_mask、page_migration); 每个zone开辟一片连续内存,通过pfn作为下标来索引对应的page_owner。通过stackdepot来管理page的调用栈信息,由调用栈计算出hash,可以快速查找到调用栈。
工具优点:可以直观查看每个已经分配的page的详细信息。
工具缺点:消耗大量内存,影响性能,因此也只能在实验室中使用。
page_owner的详细介绍和使用方法参考:https://www.kernel.org/doc/html/latest/dev-tools/kasan.html
6. vmallocinfo 工具介绍
/proc/vmallocinfo 是内核原生维测功能,记录从[VMALLOC_START, VMALLOC_END]区间已经分配的所有内存。可以通过它来分析vmalloc内存泄漏问题。
工具优点:默认使能,默认编译在内核中。
工具缺点:没有记录caller,没有记录调用栈(只有一层函数),没有进程信息。
7. 工具与对应的应用场景
内核态内存异常 工具 Slab内存泄漏 SLUB_DEBUG/kmemleak Slab越界访问,野指针访问 KASAN && SLUB_DEBUG 黑洞内存泄漏 page_owner 全局变量/栈越界访问,野指针 KASAN vmalloc内存泄漏 vmallocinfo/kmemleak
注:linux系统中有部分内存是通过直接操作page申请的,/proc/meminfo无法统计到这部分内存,就像黑洞一样。黑洞内存可以使用 cat /proc/meminfo 中的值进行计算,黑洞内存 = MemTotal - MemFree - Active - Inactive - Slab - KernelStack - PageTables - VmallocUsed
8. kmalloc分配的内存的泄漏属于slab内存泄漏。
9. 内核内存相关节点及命令
/proc/meminfo 查看系统整体内存概况。低内存场景可查看。可用于分析系统内存分步情况,比如anon占多少,slab占多少等。
dumpsys meminfo 查看具体哪些进程占用内存或某个进程占用内存的具体分步。内存去哪了场景使用。可分析各进程内存占用情况,比如anon占用2GB,具体是哪些进程,分别占用多大。
/proc/pagetypeinfo 查看各阶(2^n)内存分布情况,评估系统内存碎片情况。内存分配慢,确定内存碎片场景使用。分析各阶空闲内存,确认是否没有大块空闲连续内存。
/proc/vmstat 查看整机内存性能相关指标,如系统内存回收次数(allocstall)、内存碎片整理次数(compact_stall)、IO读总量(pgpgin)、IO写总量(pgpgout)。评估整机基础性能场景使用。用于分析各指标在各场景的变化情况,比如内存回收次数在相机场景回收了多少次,内存碎片整理了多少次。
/proc/vmallocinfo
/proc/slabinfo
free
/proc/zoneinfo
/proc/sys/vm 目录
/sys/block/$dev_name 块层的众多文件
/dev/memcg/ 众多配置文件
1. 页与页帧
将虚拟地址分为等长的小粒度块,成为页。将物理地址分为等长的小粒度块,成为页帧。应用换出时以页为单位。
2. 内存分配-伙伴系统
伙伴系统是Linux最基本的内存分配系统,能有效改善内存碎片问题。内核中常用的分配物理内存的接口是 alloc_pages() 用于分配一个或多个物理页,分配的页数只能是2^n,如果每个页是4K,那么每次能分配的最大连续物理内存是2^10也就是4M。
3. 内存分配-slab分配算法
伙伴系统分配内存是以page为单位,slab分配是以Byte为单位,slab是为了解决小内存分配需求的,内核中大量使用slab机制的一个接口是kmalloc。
//创建slab描述符 struct kmem_cache *cmbe_cache = kmem_cache_create("cmbe_cache", sizeof(struct cmbe), __alignof__(struct cmbe), 0, NULL); //分配缓存对象 struct cmbe *cmbe = kmem_cache_zalloc(cmbe_cache, GFP_KERNEL); if (!cmbe) return -ENOMEM; //释放缓存对象 kmem_cache_free(cmbe_cache, cmbe); //释放slab描述符 kmem_cache_destroy(cmbe_cache);
3. 内存分配-ION内存分配
ION内存分配是一套内存分配框架,Android4.0引入,在多媒体领域使用最多。ION将各种内存分配纳入统一的管理接口之中,目的是为了内存在不同用户态进程之间传递和访问提供支持。
举例:app1通过调用 ION_IOC_SHARE 创建与 ion_buffer 关联的 dma_buf 结构,并创建fd与 dma_buf 关联。app2通过binder机制,得到app1创建的fd,然后调用ION_IOC_IMPORT,通过fd找到 dma_buffer,并创建 ion_buffer 与 dma_buffer 的关联。这样app1就可以和app2共享 ion_buffer。
4. 内存回收- kswapd & direct reclaim
kswapd是异步内存回收线程,当空闲内存低于low水线时被唤醒,回收到空闲内存高于high水线时睡眠。direct reclaim为同步内存回收线程,当空闲内存低于min水线时,进程同时进入direct reclaim。当空闲页面低于low水线时,此时内存分配由于需要回收才能满足,因此该过程称为慢速路径(slow path)
注:high水线 > low水线 > min水线。
5. Linux中物理内存的每个zone都有自己独立的min, low和high三个档位的watermark值,在代码中以 struct zone 中的 ulong _watermark[NR_WMARK] 来表示。在进行内存分配的时候,如果分配器(比如buddy allocator)发现当前空余内存的值低于"low"但高于"min",说明现在内存面临一定的压力,那么在此次内存分配完成后,kswapd将被唤醒,以执行内存回收操作。在这种情况下,内存分配虽然会触发内存回收,但不存在被内存回收所阻塞的问题,两者的执行关系是异步的(之前的kswapd实现是周期性触发)。如果内存分配器发现空余内存的值低于了"min",说明现在内存严重不足。这里要分两种情况来讨论,一种是默认的操作,此时分配器将同步等待内存回收完成,再进行内存分配,也就是direct reclaim。还有一种特殊情况,如果内存分配的请求是带了PF_MEMALLOC标志位的,并且现在空余内存的大小可以满足本次内存分配的需求,那么也将是先分配,再回收。
参考:https://zhuanlan.zhihu.com/p/73539328
1. 内存优化维度:内存分配、内存碎片、内存压缩、内存回收、文件缓存、后台查杀。
2. 内存参数与调优
(1) /proc/sys/vm/min_free_kbytes
含义为min水位。增大该值相当于增大min水线,memfree可能提升,但是普通分配请求可能更容易进入direct reclaim。
(2) /proc/sys/vm/extra_free_kbytes
额外增加在low和min之间的内存,保持min值不变的情况下,让low值有所增大,可应对突发内存需求减少进入direct reclaim的次数。增大该值会使low和high水线都增加,一定程度上可以改善突发性内存需求而导致的进入direct reclaim的次数,可以根据实际情况增大或者减少。
(3) /proc/sys/vm/swappiness
控制kswapd回收匿名页的活跃程度。增大可以更活跃的回收匿名页,增加文件页,但是回收匿名页相对于回收文件页较慢。
(4) /proc/sys/vm/watermark_boost_factor
用于优化内存碎片,临时提高水位,默认值是15000表示会临时提高high水线到150%。若是想让watermark_boost导致kswapd执行时间短一点,可以适当调小,若是想多回收一些,可以适当调大。
(5) /proc/sys/vm/dirty_writeback_centisecs
内核回写文件页的线程周期性唤醒时间,默认值为5s。调大可以减少回写频率,对一些IO性能较差的情况,可能存在突然写入导致的IO峰值,可以调小一点让IO性能更平滑,但是可能对flash寿命有影响。
3. 文件缓存blockio优化
systrace中常看到橙色的blockio,通常是卡在 wait_on_page_bit_common。一般是文件缓存被回收,需要重新从磁盘加载导致。若频繁回收加载出现类似颠簸的现象。
执行路径:
分配一个page内存:__page_cache_alloc --> 放入page cache LRU中,add_to_page_cache_lru --> 设置page的PG_locked位,此时page的内存是空的,不可用__lock_page --> 调用block层的readpages最终调用到文件系统的readpages --> 提交IO请求 submit_bio --> IO读完成后内容写到page中后清除PG_locked位。此时这个页才是可用的,这个过程称为block IO。
优化思路:
减低底内存概率,提升文件缓存;pin系统高频访问的文件;判断读取文件是否必须;预读优化,系统本身就有预读/sys/block/$DEVICE_NAME/queue/read_ahead_kb。
4. 后台容易被杀
lowmemorykiller 4.14之后放在用户态了。还有后台清理,lowmemkill
优化:
应用进程合理的优先级adj; 改善低内存; 查杀策略优化。
5. 应用如何避免被杀
6. kamlloc() 保证物理内存连续,vmalloc()保证虚拟内存连续,kmalloc()能分配的大小有限,一般最大为4M(与max order有关),vmalloc一般比kmalloc慢。
7. 内存页类型
MIGRATE_UNMOVABLE: 不可移动内存页,如内核分配的页。
MIGRATE_RECLAIMABLE: 可回收内存页,如文件映射产生的页。
MIGRATE_MOVABLE:可移动内存页,如用户态应用程序使用的页。
1. slub_debug介绍
背景:slub_debug就内核提供的一种内存调试配置选项。其在监测内存越界(out-of-bounds)和访问已经释放的内存(use-after-free)等问题时效果非常好。
使用场景:仅限于kernel space
配置中需要打开:
COPNFIG_SLUB=y COPNFIG_SLUB_DEBUG=y COPNFIG_SLUB_DEBUG_ON=y COPNFIG_SLUB_STATS=y
2. ASAN介绍
ASAN(Address Sanitizier) 最早是LLVM中的特性,后被加入GCC 4.8,在GCC 4.9后加入对ARM平台的支持。因此GCC 4.8以上的版本使用ASAN时不需要安装第三方库,通过在编译时指定编译CFLAGS即可打开开关。
Android.mk中加上:
LOCAL_CLANGS:=true LOCAL_SANITIZE:=address LOCAL_CFLAGS:= -fno-omit-frame-pointer
使用场景:仅限于user space。
作用:针对进程内存越界、访问已经释放的内存debug。
执行时,会打印出出错内存的调用栈,是Native C代码的调用栈
3. KASAN
KASAN(Kernel Address Sanitizier) 的缩写,是一个动态监测内存错误的工具,主要功能是检查内存越界访问和使用已经释放的内存等问题。Kasan集成在Linux内核中,随linux内核代码一起发布,并由内核社区维护和发展,其功能与slub_debug有一定重复,但kasan发现问题更快。
使用场景:仅限于kernel space。
4. Kmemleak
是内核提供的一种监测内存泄漏的工具,启动一个内核线程扫描内存,并打印发现新的未引用对象的数量。
使用场景:仅限于kernel space。主要debug内核内存泄漏。
需要打卡config:
CONFIG_HAVE_DEBUG_KMEMLEAK=y CONFIG_DEBUG_KMEMLEAK=y CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=400 CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=n
echo scan > /sys/kernel/debug/kmemleak 触发检测,若检测到内存泄漏,cat /sys/kernel/debug/kmemleak 会看到泄漏字节数、相关进程的信息、和调用栈信息。