飞道的博客

【Android 内存优化】垃圾回收算法 ( 内存优化总结 | 常见的内存泄漏场景 | GC 算法 | 标记清除算法 | 复制算法 | 标记压缩算法 )

504人阅读  评论(0)





一、 内存优化总结



内存泄漏原理 : 长生命周期对象 , 持有短生命周期对象的引用 , 并且是强引用持有 , GC 无法释放该短生命周期对象引用 , 造成 OOM ;

Android Profiler 工具参考官方文档 : 使用 Memory Profiler 查看 Java 堆和内存分配


使用 Memory Analyzer ( MAT ) 内存分析工具分析内存快照 , 首先要将内存快照文件 , 转化成 MAT 工具能识别的文件 , 然后使用 MAT 工具进行分析 ;


在博客 【Android 内存优化】Android Profiler 工具常用功能 ( 监测内存 | 内存快照 ) 中保存了内存快照文件 memory-20200625T145636.hprof , 要使用 MAT 工具分析该内存快照 , 需要先将该文件转换成为 MAT 标准的文件格式 ;


在博客 【Android 内存优化】使用 Memory Analyzer ( MAT ) 工具分析内存 ( hprof 文件转换 | MAT 工具下载 | MAT 工具使用 ) 中转换了 MAT 格式的内存快照 , 下载 Memory Analyzer ( MAT ) 内存分析工具 , 并在该工具中加载了 MAT 格式的文件 ;


在博客 【Android 内存优化】使用 Memory Analyzer ( MAT ) 工具分析内存 ( MAT 工具使用 | 最大对象 | 类实例个数 | 引用与被引用 | GC Roots 最短链 )使用 Memory Analyzer ( MAT ) 内存分析工具 中分析内存快照 , 主要是查看 GC Roots 最短链 , 分析出在哪个类中引用了该对象 ;





二、 常见的内存泄漏场景



内存泄漏的常见原因 :

  • 集合的使用
  • 静态成员
  • 常量
  • 单例模式 : 不要在单例中随便持有 Context , Activity 之类的成员 , 有极大的内存泄漏隐患 ;
  • 没有释放或关闭的资源 : 如 IO 流 , Socket 等 ;
  • 线程 : 界面退出 , 线程没有退出 , 线程持有的引用就泄漏了 ; 尽量在其中使用弱引用 ;
  • Handler : 非静态内部类造成内存泄漏 ;




三、 内存回收算法



1. 内存抖动 : 应用对象的内存 , 频繁的分配 , 回收 , 造成内存使用量上下抖动 , UI 卡顿 , 严重时甚至造成 OOM ( OutOfMemoryError ) , 造成内存溢出 ;


2. 内存抖动造成溢出原因 : 对象频繁分配 , 回收 , 会大量造成内存的空隙 , 这些空隙很小无法分配大块内存 , 当整个内存都是这种空隙时 , 无法为大块内存分配空间 , 就造成了 OOM 异常 ;


3. GC 垃圾回收之前 , 需要对内存对象进行采集 , 不同的虚拟机使用不同的垃圾回收算法 , 常用的垃圾回收算法 :

  • 标记-清除算法 ( mark-sweep )
  • 复制算法
  • 标记-压缩算法
  • 分代收集算法




四、 标记-清除算法 ( mark-sweep )



标记-清除算法 ( mark-sweep ) : 步骤分为两步 : ① 标记 , ② 清除 ;


内存中分为如下几块 :

  • 可回收对象
  • 存活对象
  • 可用内存

标记-清除算法 ( mark-sweep ) 算法中 , 首先标记出可回收对象 , 标记完成之后 , 统一回收 ;

回收完毕后 , 存活的对象仍然保持在原来的位置 , 可用内存基本支离破碎 , 这样就会造成内存碎片 , 这些内存碎片中无法申请大块内存 ;

上图中的内存中 , 有 24 个格子的空闲内存 , 如果要申请 5 5 个单位格子的内存 , 发现无法申请 , 没有连续 5 个格子的内存 , 此时直接出现 OOM ;

有很多内存, 但都是支离破碎的 , 没有大块内存 ;





五、 复制算法



1. 复制算法 : 将可用内存 , 分为两个想等于内存区域块 , 区域 1 1 区域 2 2 , 使用时只使用其中的一个区域 ;

  • 垃圾回收前 , 只使用区域 1 1 的内存
  • 垃圾回收后 , 将区域 1 1 的内存中可用对象复制到区域 2 2
  • 复制时的可用对象在区域 2 2 紧密排列 , 不留空隙
  • 这样区域 2 2 中可用内存区域是大块完整的内存 , 不会产生内存碎片

当前使用区域 1 1 的内存区域内存不足时 , 会触发上述操作 , 将当前区域 1 1 的存活对象 , 拷贝到区域 2 2 中 , 然后清理区域 1 1 内存 ;

分配回收内存时 , 只需要按照顺序移动堆指针即可 , 不考虑碎片化等问题 , 简单 , 高效 ;



2. 弊端 :


该垃圾回收算法缺陷也很明显 , 就是会浪费一半内存空间 ;

有些对象的声明周期等同于应用声明周期 , 如 Android 中的 Application 等 , 该内存对象根本不释放 , 持续往返复制这类长生存期的对象 , 会极大降低效率 ;





六、 标记-压缩算法



1. 标记压缩算法 : 与标记清除算法都需要先进行标记 ;


2. 标记压缩算法流程 :

  • 首先标记可回收对象
  • 然后回收这些对象
  • 最后整理存活对象 , 将其拷贝到一块连续内存中

该方法没有复制算法浪费一半内存的问题 ;

该方法因为多了一个压缩过程 , 因此有额外的开销 ;


转载:https://blog.csdn.net/han1202012/article/details/106961915
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场