内存优化

内存问题和优化总结

2019-07-28  本文已影响0人  AerialLadder

一. 内存问题

1.概念:
VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS- Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS- Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)


内存1.png
  1. 内存问题会导致两个结果,一个是异常,如OOM,内存分配失败等。
    第二个问题是卡顿。Java内存不足会导致频繁GC。

测试GC性能可以通过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. 两个误区:
    误区1:内存占用越少越好
    应用是否占用了过多内存跟设备、系统和当时环境有关,不是300MB这样的绝对数值。要做到的是“用时分配,及时释放”

Bitmap内存优化
Android3.0之前Bitmap对象存放在Java堆,像素数据在Native内存中,如果不手动调用recyle,native内存的回收时机不可控。
Android3.0~7.0将Bitmap对象和像素数据统一存放到Java堆中,这样就算我们不调用recycle,Bitmap内存也会随着对象一起被回收。由于Java堆内存有限制,所以也可能会导致OOM,并且容易引起大量GC。
Android8.0采用NativeAllocationRegistry。一种实现 可以将 Bitmap内存放到 Native中 也可以做到和对象一起快速释放 同时GC的时候也能考虑到这些内存防止被滥用
详细了解可以参考这篇文章Android Bitmap变迁与原理解析(4.x-8.x)

误区二:Native 内存不用管
当系统物理内存不足额时候,lmk剋是杀进程,甚至可能导致手机重启。

Fresco 图片库在 Dalvik 会把图片放到 Native内存上。在Android5.0~7.0上也能做到,只是流程相对复杂。

// 步骤一:申请一张空的 Native Bitmap.直接调用 libandroid_runtime.so 中 Bitmap的 
//构造函数饿到内存在native的Bitmap对象。到那时Android版本的实现差异需要适配
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;

  1. 查看内存使用情况
    可以参考《调查 RAM 使用情况》
adb shell dumpsys meminfo <package_name|pid> [-d]

工具:Allocation Tracker
不过他有三个缺点:https://mp.weixin.qq.com/s/b_lFfL1mDrNVKj_VAcA2ZA?

  1. 自定义的“Allocation Tracker”
    要考虑的兼容性问题比较多。下面是在 Dalvik 和 ART 中,Allocation Tacker 的开启方式。
// dalvik
bool dvmEnableAllocTracker()
// art
void setAllocTrackingEnabled()

https://github.com/AndroidAdvanceWithGeektime/Chapter03

  1. Android8.0
    AddressSanitizer
    指南
    调试本机内存使用Malloc 调试和Malloc 钩子。

二. 优化

1. 设备分级

使用类似device-year-class的策略堆设备进行分级,对低端机关闭复杂动画,使用小内存缓存等

if (year >= 2013) {
    // Do advanced animation
} else if (year >= 2010) {
    // Do simple animation
} else {
    // Phone too slow, don't do any animations
}
2. 缓存管理

尽量用统一的一套缓存机制,在内存不足的时候,如OnTrimMemory 回调,根据不同的状态释放一部分内存。

3. 进程模型

一个空进程也会赵勇10MB内存,减少进程数 。

4. 安装包大小

安装包中的代码、资源、图片以及so库的体积,跟占用的内存有很大的关系。参考表格


内存2.png
5. Bitmap优化
6. 简单的说就是没有回收不再使用的内存

内存泄漏分为两种情况: 同一个对象泄漏 、泄漏新的对象

7. 内存监控
  1. 采集方式
    用户在前台的时候 可以每5分钟采集一次PSS(比例分配共享库占用的内存 ) Java堆 图片总内存
    建议只统计部分用户
  2. 计算指标
    内存异常率 可以反映内存占用的异常情况
    PSS 的值可以通过 Debug.MemoryInfo 获取
内存 UV 异常率 = PSS 超过 400MB 的 UV / 采集 UV

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

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

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

long javaMax = runtime.maxMemory();
long javaTotal = runtime.totalMemory();
long javaUsed = javaTotal - runtime.freeMemory();
// Java 内存使用超过最大限制的 85%
float proportion = (float) javaUsed / javaMax;

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

  1. 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的次数和耗时 它会暂停应用线程 可能导致应用发生卡顿

上一篇 下一篇

猜你喜欢

热点阅读