文章目录
一、 内存优化总结
二、 常见的内存泄漏场景
三、 内存回收算法
四、 标记-清除算法 ( mark-sweep )
五、 复制算法
六、 标记-压缩算法
一、 内存优化总结
内存泄漏原理 : 长生命周期对象 , 持有短生命周期对象的引用 , 并且是强引用持有 , 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 55 个单位格子的内存 , 发现无法申请 , 没有连续 5 个格子的内存 , 此时直接出现 OOM ;
有很多内存, 但都是支离破碎的 , 没有大块内存 ;
五、 复制算法
1. 复制算法 : 将可用内存 , 分为两个想等于内存区域块 , 区域 1 11 和 区域 2 22 , 使用时只使用其中的一个区域 ;
垃圾回收前 , 只使用区域 1 11 的内存
垃圾回收后 , 将区域 1 11 的内存中可用对象复制到区域 2 22
复制时的可用对象在区域 2 22 紧密排列 , 不留空隙
这样区域 2 22 中可用内存区域是大块完整的内存 , 不会产生内存碎片
当前使用区域 1 11 的内存区域内存不足时 , 会触发上述操作 , 将当前区域 1 11 的存活对象 , 拷贝到区域 2 22 中 , 然后清理区域 1 11 内存 ;
分配回收内存时 , 只需要按照顺序移动堆指针即可 , 不考虑碎片化等问题 , 简单 , 高效 ;
2. 弊端 :
该垃圾回收算法缺陷也很明显 , 就是会浪费一半内存空间 ;
有些对象的声明周期等同于应用声明周期 , 如 Android 中的 Application 等 , 该内存对象根本不释放 , 持续往返复制这类长生存期的对象 , 会极大降低效率 ;
六、 标记-压缩算法
1. 标记压缩算法 : 与标记清除算法都需要先进行标记 ;
2. 标记压缩算法流程 :
首先标记可回收对象
然后回收这些对象
最后整理存活对象 , 将其拷贝到一块连续内存中
该方法没有复制算法浪费一半内存的问题 ;
该方法因为多了一个压缩过程 , 因此有额外的开销 ;