性能优化(三)-内存优化2

449 阅读9分钟

1、进程指标

image.png us用户空间占百分比

sy内核空间占百分比

ni用户进程空间内该百年过优先级的进程占用CPU百分比

id cpu空闲百分比

wa 等待输入输出的CPU的时间的百分比

hi 硬中断

si软中断

ir 中断

2、内存哪里去了

  • MemTotal:        2042260 kB
  • MemFree:      170764kB
  • MemAvailable:     587648 kB
  • Buffers:      14256kB
  • Cached:           570504 kB
  • Memtotal 除了boot 内核 内存,剩下可供系统使用的内存
  • Memfree 未被使用的内存
  • MemAvailable 统中有些内存虽然已被使用但是可以回收的,比如cache/buffer、slab都有一部分可以回收,所以MemFree不能代表全部可用的内存,这部分可回收的内存加上MemFree才是系统可用的内存,即:MemAvailable≈MemFree+Buffers+Cached,它是内核使用特定的算法计算出来的,是一个估计值。它与MemFree的关键区别点在于,MemFree是说的系统层面,MemAvailable是说的应用程序层面

MemAvailable≈MemFree+Buffers+Cached

3、常用工具

  • mat
  • profile

4、LeakCanary

  • 原理看我的这篇文章LeakCanary的实现原理 - 掘金 (juejin.cn)
  • 分析时容易oom
  • 只对activity、fragment进行预设埋点,需要自己定制源码,针对对象进行裁剪
  • 不全加载到内存中,只适合线下监控

5、线上内存检测方案的思路

  • leakcanary或者matrix,在线下监控。
  • 使用koom,fork dump,不影响主进程的运行。
  • 自带的 memory monitor进行内存监控
  • 用MAT分析内存泄漏问题。
  • 对activity泄漏,gc后进行dump hprof操作。
  • hprof文件导入到 MAT查看activity的 引用链条。找出静态成员 或者长生命周期引用。
  • 对于Bitmap,Hprof导入到Android Monitor,就可以找出来。

6、activity在执⾏销毁的时候 我们如何得知?

  • 第一种lifecyclecallback。手动触发GC,会造成用户卡顿
  • 第二种就是阈值处理,达到标杆才触发GC,可以用count计数,这里细节可以再补充koom就在这里做。

7、如何判断⼀个Activity⽆法被GC机制回收?

  • 用弱引用处理,referencequeue(后续上代码)

8、内存泄漏的常用原因

  • 静态变量或单例持
  • 内部类 匿名内部类 持有外部内引用
  • 动画在activity 销毁时 没cancel
  • IO Cursor File没有close
    image.png

9、koom 简单描述

  • native heap 内存泄漏监控,主要借助了 tracing garbage collection ,G1算法,解决堆内存检测的一种方案(”可以细化“)。

  • 使用之后放到项目中,可把指标放到项目中

9.1、怎么去检测native层的内存泄漏呢?
  • 第一种 自己去做 hook,很难。
  • 第二种 ,libmemunreachable+xhook
  • -xhook去hook malloc/free等方法,记录native内存分配元数据
    • libmemunreachable去周期性使用mark-and-sweep获取不可达内存块信息

通过不可达内存块信息&元数据分配堆栈信息,可得泄漏数据信息.

9.2 koom总结

1、要知道libmemunreachable这个概念 2、要知道 koom如何做内存native的检测 3、方案一定要懂 4、频繁GC会导致用户可感知的卡顿。koom采用了无性能损耗的内存阈值监控。要引导面试官。 泄漏可能性达到90%。再来触发GC,提高性能。

10、内存检测思路

内存检测2种思路。

  • 思路一: 利用弱引用的特性,如leakcanary/Matrix。当Activity对象编程弱可达时(没有强引用了),弱应用会被加入到引用队列中。Activity.onDestory后连续触发两次 GC,并检测引用队列,则可以 判定Activity是否发生了泄漏。但是频繁GC会造成用户可感知的卡顿。所以有第二种方法。
  • 思路二: 阈值检测的方法。如koom,通过无性能损耗的内存阈值监控来触发镜像采集,这个进行采集就是fork子进程来dump。

image.png

11、dump Hprof

adb shell am dumphead

dump 会有个scopesuspendall方法来暂停所有的线程,导出hprof会很卡,而且文件很大。这对用户来说,不友好。

于是用fork子进程去dumpHprofData,fork采用 了copyonwrite的机制,原本父子享用同空间,但是如果写入则拷贝分配独立空间,也就拥有了父进程的副本,再执行dumpHprofData方法,父进程正常,不受影响。

image.png

12、动态链接库

  • 核心知识点-》了解动态链接库的打开和关闭
  • koom中dump会用到动态链接库,也就是jni中c调so的函数。具体学习可以看下链接 linux.die.net/man/3/dlope…

13、Profile剪裁问题

  • 读取Hprof文件
  • 记录Bitmap和String类信息(why)
  • 移除Bitmap buffer和String value之外的基础类型数组
  • 将同意张照片的Bitmap bugger指向同一个 buffer id,移除重复的Bitmap buffer
  • 将数据原封不动地输出到新文件中。

14、Matrix

  • Matrix的内存泄漏监控是由ResourceCanary实现的
  • 准确说ResouceCanary是检测Activity内存泄漏的
  • 但activtiy内存泄漏是可以dump一个堆转储文件,通过文件可以分析是否存在相同的Bitmap。
  • ResourceCanary,通过WeakReference特性和haha库开发 Activity内存泄漏检测和同Bitmap创建。
  • 当一个activity的mDestory为true,但是haha通过dump hprof 来做引用链分析,activity还是被引用。那么就表示泄漏了

15、OMM问题

1、java堆内存溢出

2、虚拟内存不够

3、无足够连续内存空间

4、FD数量超标

5、线程数超标

16、MAT中的重要概念

incoming references:C类的对象被A、B类引用,那么AB是C的incoming references outgoning references:C类中引用了D、E对象。那么DE是C的outgoning references

17、Android中加载一张图片所占用内存

  • 如果粗略来讲就是:像素长度* 像素宽度 * 一像素字节大小,通常8888,4个字节。也有565。是2个字节。
  • 如果严谨一点:(收集项目密度dpi/图片所放入的文件夹对应的像素密度dpi)^2)像素长度 像素宽度 * 一像素字节大小

18、压缩bitmap的小细节

try{
decode bitmap
}catch(outOfMemoryError e){
    质量压缩
}

19、bitmap优化思路

总体思想: 1、设备分级,针对不同设备有不同的方案。

2、Bitmap优化 统一图片库 线上/线下监控

3、具体优化看官网。

20、leakcanary

具体可以看我的这篇文章,LeakCanary的实现原理 - 掘金 (juejin.cn)

过了5秒gc,再到引用队列里找一找看能不能找到引用。如果找到我就从watechedreferences中把他remove掉。如果还在我就加到怀疑列表中,retainedReferences。当怀疑列表个数大于5个就调用haha库的可达性分析来确认内存是否泄漏了

比如Activity是否泄漏:判断实例是否是android.app.Activity 的子类,并且 mFinished 或 mDestroyed 是否为true(Activity 关闭时该值会为 true),因为Activity 不泄露的话肯定是会被释放,所以,不可能存在于dump的实例中,有就是发生了泄漏

然后通过引用链找到引起泄漏的元凶

21、其它小点

  • runtime.getRuntime.GC不一定执行
  • Matrix 检测到activity泄漏,则再ondestory中将素有成员和view置空。那么泄漏的也只是一个空壳问题不大。
  • activity可以实现接口来收到 内存等级不够的通知。 implements componentCallbacks2

image.png

  • 做内存优化,MAT的使用、profiler官方使用教程、koom一定要学。后续我出个教程

22、内存泄漏的原因

  1. 对象未及时释放:在Android开发中,如果创建了过多的对象,而这些对象没有被及时释放,就会导致内存泄漏。例如,在Activity或Fragment的生命周期中,如果持有过多的Bitmap对象,会导致Activity或Fragment无法被垃圾回收器回收,从而导致内存泄漏。
  2. 持有Context引用:在Android开发中,Context是一个非常重要的对象,但它也容易导致内存泄漏。如果一个对象持有Context的引用,而这个对象又不能被垃圾回收器回收,就会导致内存泄漏。例如,在Activity中持有Context的引用,但这个引用没有被及时释放,就会导致Activity无法被垃圾回收器回收,从而导致内存泄漏。
  3. 循环引用:在Android开发中,如果两个对象之间存在循环引用,即对象A持有对象B的引用,而对象B又持有对象A的引用,就会导致内存泄漏。例如,在Activity中存在一个View对象,而这个View对象又引用了Activity对象,就会导致Activity无法被垃圾回收器回收,从而导致内存泄漏。
  4. 资源未及时释放:在Android开发中,如果使用了大量的资源,如文件、数据库、网络连接等,而这些资源没有被及时释放,就会导致内存泄漏。例如,在使用Cursor时没有及时关闭Cursor对象,就会导致Cursor占用的内存无法被垃圾回收器回收,从而导致内存泄漏。
  5. ** 线程未正确管理**:在Android开发中,如果线程没有正确管理,也会导致内存泄漏。例如,在线程中创建了大量的对象,而这些对象没有被及时释放,就会导致内存泄漏。
  6. ** 静态变量**:在Android开发中,如果一个类中存在过多的静态变量,而这些变量没有被及时释放,就会导致内存泄漏。例如,在一个静态变量中引用了大量的对象,而这些对象没有被及时释放,就会导致内存泄漏。
  7. ** webview**
为了防止内存泄漏问题,可以采取以下措施1:
  1. 及时释放不再使用的对象:在开发过程中,要注意及时释放不再使用的对象,包括Bitmap、Context、Cursor等。
  2. 使用弱引用技术:对于一些不重要的对象,可以使用弱引用技术来避免对象的循环引用。例如,在Activity
泄漏 大概有Bitmap和内存泄漏,逻辑内存泄漏