小程序/H5中资源与内存使用关系梳理:高清大图内存主要分配在gpu上,随便下载了一些文章详情的图片发现大小都在1MB以内,这是因为图片都是有压缩的,但是在手机中图片显示是完全解压状态,正常情况图片的内存计算方式:
- png = 像素总数(图片宽×高) × 4 Byte per pixel × 1~3(渐进式、缩放缓存、纹理尺寸、mipmap限制等因子影响),因此高清大图应控制分辨率,按需使用适当尺寸的图片,禁止超高分辨率图片
- gif图片内存主要分配在Gpu上,由于gif采用多帧轮播的方式播放,gif所有帧内存都会加载到gpu中且无法释放,其内存计算方式是:
- GIF = 像素总数(图片宽×高) × 4 Byte per pixel × 总帧数 × 1~2(纹理尺寸、mipmap限制等因子影响)
- 典型案例:分辨率690*4746,帧数21,文件大小346KB直接占用GPU 210MB,该图仅最上面是动图,而全图使用gif格式并且以超长图展示,线下测试手机需要至少10s才能加载出该图。应该严格限制此类情况发生
在监控内存水位的同时,我们还要掌握每个业务生命周期内分配的内存空间是否合理,目前做法是通过开关配置一组内存指标异常值,当业务单次采样间隔内存分配超过阈值时上报相关数据,概数据帮助我们在线下推动业务对内存使用优化,同时配合线上告警机制保证业务内存使用变化时能快速感知到。
典型案例:线上监控到某选图打印的小程序单次的Libcmalloc增幅超过300M,导致该应用OOM闪退率是正常业务的20倍,主要原因有2点:
- 用户在使用小程序时会选择一张手机图片,在多媒体选图组件做图片预览和大图展示时为了加快图片展示提前加载了选图前后2张,由于图片加载内存全部分配在Native导致Libcmalloc内存大幅上涨,个别用户涨幅超过1G
- 多媒体组件没有监听小程序退出事件,因此没有及时做内存回收导致缓存的图片数据长时间在内存中保存
支付宝微镜平台提供了闪退issue的查看和分析能力,我们只需在客户端闪退时按照一定格式将端上运行时数据记录到埋点即可在平台查看到聚合后的数据
- 临近闪退时的内存snapshot及闪退时刻内存数据记录,方便排查闪退前的内存变化趋势和闪退时内存数据聚合分析
- 部分maps原始地址数据,隐藏了内存中异常信息
- 完整的虚拟地址解析后结构,如下图
Moudles
Moudles-Ashmem
Moudles-JavaVM
Moudles-Native-Anonymous
Moudles-Native-Bss
Moudles-Native-LibcMalloc
Moudles-Native-Other
Moudles-Other
Public
Public-Devices
Public-Files
Public-Gpu
Public-Threads
U4Core
U4Core-BlinkGC
U4Core-PartitionAlloc
U4Core-SharedMemory
U4Core-UCMalloc
U4Core-v8 Heap
典型案例:
- MIUI Android 10 系统字体重复占用虚拟内存,导致支付宝 App 可用的虚拟内存变少,更容易发生崩溃(发生OOM)。支付宝在20年的双十一活动的 OOM 中,小米 红米机型占比 43%,高于小米手机的市占率
- 某外卖小程序在使用v8 worker的时候调用message port发给render进程大量消息并使用了sharedmemory导致闪退率上涨
支付宝是一个汇集了扫码,端智能,AR,小程序容器,音视频播放等 native 代码非常多的大型应用,据不完全统计有100个so在支付宝内运行,每个 native 代码相关的模块背后都有一个专业团队在高速迭代,Java 堆内存因为有比较成熟的工具和方法论,加上 hprof 快照作为补充,定位和治理都很方便,native 内存问题一直缺乏高效稳定的工具导致问题治理难度非常大。事实上,单纯的native内存泄漏问题相对较少,更多的是因为业务逻辑不合理带来的内存使用问题,需要工具渗透到 App 运行的过程中进行监控,对工具性能和稳定性提出更高的要求。
因此对于支付宝的 native 内存泄漏监控工具的诉求主要有以下几个方面:
- 接入成本:无需额外适配Android 版本,无需 root
- 稳定性:几乎不影响业务的稳定性问题,可以满足灰度版本开启即可
- 性能层面:对业务运行和启动都没有明显劣化影响,无卡顿和ANR问题
- 监控范围:不局限于 malloc/calloc/realloc/memalign/free,至少还能覆盖 mmap/mmap64/munmap
- 支撑广度:内存监控,内存泄漏,野指针检测等
维度\产品 | SoAOP(支付宝) | malloc debug | AddressSanitizer | memhook(UC) | Native Finder(手淘) |
是否需要root | 不需要 | 需要 | 需要 | 需要 | 否 |
功能支撑广度 | 内存泄漏、So内存值监控、堆破坏、野指针 | 内存泄漏、堆破坏、野指针 | 内存泄漏、堆破坏、野指针、缓冲区溢出 | 内存泄漏检测 | 内存泄漏、堆破坏、野指针 |
堆栈能力 | 有 | 有 | 有 | 有 | 有 |
性能 | 好 | 差 | 好 | 较好 | 好 |
接入成本 | 低 | 高 | 高 | 高 | 较高 |
支付宝的 native 内存泄漏监控工具主要包含三部分:代理实现、栈回溯和缓存管理,其中代理实现是对malloc/calloc/realloc/memalign/free和 mmap/mmap64/munmap等函数的hook,决定了hook的准确程度至关重要,这里我们选择了比较成熟的PLT hook 工具代表 xHook,该方案已经在业内多个内存线上方案中使用,其稳定性可以达到上线标准。在栈回溯上使用了性能更好的libudf方案兼具了性能要求和回溯成功率,为了提高线上的使用场景,我们在使用时通过开关限制栈回溯频次
方案细节:
- 首先客户端在启动后会初始化注入hook相关的服务。
- 通过轮询扫描proc/self/maps获取支付宝包内的实时so数据,并添加so管理。
- hook的malloc/free/mmap/munmap/mmap64/pthread_exit/calloc/realloc
- 监听方法回调,根据内存指针通过malloc_usable_size方法获取对应的内存和so数据,进行统计内存。
- 线下方案:
目前线下方案通过动态bundle的方式可以扫码实时加载到支付宝客户端中,通过悬浮窗的方式展示内存数据和栈回溯信息,同时也支持接口和Logcat日志方式查看数据