美文网首页
Android内存优化

Android内存优化

作者: 34sir | 来源:发表于2019-02-13 11:05 被阅读3次

内存产生的问题

  • 异常
  • 卡顿 频繁GC 物理内存不足触发 low memory killer

通过发送SIGQUIT 信号获得ANR日志

adb shell kill -S QUIT PID
adb pull /data/anr/traces.txt

包含ANR转储信息 以及GC的详细性能信息

sticky concurrent mark sweep paused:    Sum: 5.491ms 99% C.I. 1.464ms-2.133ms Avg: 1.830ms Max: 2.133ms     // GC 暂停时间

Total time spent in GC: 502.251ms     // GC 总耗时
Mean GC size throughput: 92MB/s       // GC 吞吐量
Mean GC object throughput: 1.54702e+06 objects/s 

两个误区
误区1 内存占用越少越好
系统内存足时多用一些以获得更好的性能;
内存不足时 希望用时分配 及时释放

关于Bitmap内存分配的变化

  • Android 3.0之前 Bitmap对象存在Java堆 像素数据存在Native内存中
    不手动调用recycle Bitmap Native内存的回收完全依赖finalize 时机不太可控
  • Android 3.0~Android 7.0 Bitmap对象和像素数据统一放到Java堆中 不调用recycle Bitmap内存也会是随对象一起被回收
    不过Bitmap消耗内存太大 还是不合适
  • Android 8.0 利用NativeAllocationRegistry实现像素数据存放到Native中 并且新增了硬件位图 Hardware Bitmap 可以减少图片内存并且提升绘制效率
    NativeAllocationRegistry:
    一种实现 可以将 Bitmap内存放到 Native中 也可以做到和对象一起快速释放 同时GC的时候也能考虑到这些内存防止被滥用

误区二: Native内存不用管
将图片的内存放到 Native

// 步骤一:申请一张空的 Native Bitmap
Bitmap nativeBitmap = nativeCreateBitmap(dstWidth, dstHeight, nativeConfig, 22);

// 步骤二:申请一张普通的 Java Bitmap
Bitmap srcBitmap = BitmapFactory.decodeResource(res, id);

// 步骤三:使用 Java Bitmap 将内容绘制到 Native Bitmap 中
mNativeCanvas.setBitmap(nativeBitmap);
mNativeCanvas.drawBitmap(srcBitmap, mSrcRect, mDstRect, mPaint);

// 步骤四:释放 Java Bitmap 内存
srcBitmap.recycle();
srcBitmap = null;

测量方法

adb shell dumpsys meminfo <package_name|pid> [-d]

Java 内存分配

自定义 Allocation Tracker 需要考虑很多兼容性问题 在 DalvikARTAllocation Tracker的处理流程差异较大

Native 内存分配

Android 8.0 之后使用 AddressSanitize 检测内存
Native内存问题的两种方法:

  • Malloc 调试
    可以帮助我们调试 Native 内存的问题 例如 堆破坏 内存泄漏 非法地址
    Android 8.0 之后支持在非root的设备做Native内存调试 和AddressSanitize 一样 需要通过 wrap.sh做包装
adb shell setprop wrap.<APP> '"LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper"'
  • Malloc 钩子
    在Android P 之后 Android 的libc 支持拦截在程序执行期间所有 分配/释放 调用 这样我们可以构建出自定义的内存检测工具
adb shell setprop wrap.<APP> '"LIBC_HOOKS_ENABLE=1"'
代码 图片 资源以及so库和内存的关系.png

怎么优化?

Bitmap优化

  • 统一图片库
    图片内存优化的前提是收拢图片的调用 这样可以做整体的控制策略
  • 统一监控
    1⃣️ 大图监控
    我们需要注意某张图片内存占用是否过大 例如长宽远远大于View
    开发过程中 检测到不合规的图片使用 应该立即弹出对话框提示图片所在的Activity和堆栈
    2⃣️重复图片监控
    重复图片指的是 Bitmap 的像素数据完全一致 但是有多个不同的对象存在

内存泄漏

简单的说就是没有回收不再使用的内存
内存泄漏分为两种情况:1⃣️ 同一个对象泄漏 2⃣️ 泄漏新的对象

  • Java内存泄漏
    建立类似 LeakCanary 自动化检测方案 至少做到ActivityFragment的泄漏检测
    内存泄漏线上优化: 对生成的Hprof 内存快照文件做一些优化 裁剪大部分图片对应的byte数组减少文件大小
  • OOM监控
    美团的组件:Probe 在发生OOM的时候生成Hprof内存快照 然后通过单独进程对这个文件做进一步分析 但是线上使用风险较大
  • Native 内存泄漏监控
    Malloc 但不是很稳定
  • 针对无法重编so的情况
    使用了 PLT Hook(Native Hook 的一种方案) 拦截库的内存分配函数 然后重定向到我们自己到实现后记录分配到内存地址 大小 来源so库路径等信息 定期扫描分配和释放是否配对 对于不配对等分配输出我们记录的信息
  • 针对可重编的so
    通过GCC的-finstrument-functions参数给所有函数插桩 桩中模拟入栈出栈操作;
    通过Id的-wrap 参数拦截内存分配和释放函数 重定向到我们自己到实现后记录分配到内存地址 大小 来源so以及插桩记录的调用栈的内容 定期扫描分配和释放是否配对 不配对的输出记录的信息

开发过程中 内存泄漏排查可以使用 Androd ProfilerMAT 工具配合使用

内存监控

采集方式

用户在前台的时候 可以每5分钟采集一次PSS(比例分配共享库占用的内存 ) Java堆 图片总内存
建议只统计部分用户

计算指标

内存异常率 可以反映内存占用的异常情况
PSS 的值可以通过 Debug.MemoryInfo 获取

内存 UV 异常率 = PSS 超过 400MB 的 UV / 采集 UV

UV 指独立访客
触顶率 可以反映Java内存的使用情况 如果超过85%最大堆限制 GC会变的更加频繁 造成OOM和卡顿

内存 UV 触顶率 = Java 堆占用超过最大堆限制的 85% 的 UV / 采集 UV

是否触顶可以通过下面的方法计算得到:

内存 UV 触顶率 = Java 堆占用超过最大堆限制的 85% 的 UV / 采集 UV

一般客户端只上报数据 所有计算放在后台

GC监控

测试环境 可以通过 Debug.startAllocCounting来监控Java内存分配和GC
对性能有影响 已经被标记为 deprecated
可以拿到内存分配的次数 大小 以及GC发起的次数

long allocCount = Debug.getGlobalAllocCount(); //内存分配次数
long allocSize = Debug.getGlobalAllocSize(); //内存分配大小
long gcCount = Debug.getGlobalGcInvocationCount(); //GC次数

Android 6.0 之后拿到更加精准的GC信息

// 运行的 GC 次数
Debug.getRuntimeStat("art.gc.gc-count");
// GC 使用的总耗时,单位是毫秒
Debug.getRuntimeStat("art.gc.gc-time");
// 阻塞式 GC 的次数
Debug.getRuntimeStat("art.gc.blocking-gc-count");
// 阻塞式 GC 的总耗时
Debug.getRuntimeStat("art.gc.blocking-gc-time");

需要特别注意阻塞式GC的次数和耗时 它会暂停应用线程 可能导致应用发生卡顿

相关文章

网友评论

      本文标题:Android内存优化

      本文链接:https://www.haomeiwen.com/subject/xmydsqtx.html