1、内存占用高导致的问题

  • 1、内存泄漏导致OOM崩溃
  • 2、界面卡顿,影响用户体验
  • 3、高内存耗电,被系统及安全软件警告,容易被卸载或后台关闭

之所以要内存优化为了更好的适应JVM的GC机制,减少对程序的影响(减少卡顿)。

2、JVM 内存模型

Minor GC 使用标记-清除-复制算法,将Eden区和From Survivor区中存活下来的对象复制到 To Survivor区中,然后清空Eden区和From Survivor区,速度快。

Major GC 采用标记-清除-压缩算法,即,先标记无用的对象,然后将存活下来的对象移动,整理碎片,以腾出更大的连续空间。

如下图所示:

%title插图%num
JVM.png

3、GC原理及简介

采用是否根节点可访问判断是否需要回收对象,如下图所示

%title插图%num
GC.png
3.1标记清除复制算法
%title插图%num
mark-copy.png
3.2标记清除复制算法
%title插图%num
mark-press.png

4、GC Reason和Name

<pre class=”hljs undefined” data-original-code=”” reason”=”” data-snippet-id=”ext.fad1dd2cb080f9ce19557e96ccdfb127″ data-snippet-saved=”false” data-codota-status=”done”>

  1. Reason
  2. Concurrent—-后台回收内存,不暂停用户线程
  3. Alloc—-当app要申请内存,而堆又快满了的时候,会阻塞用户线程
  4. Explicit—-调用Systemt.gc()等方法的时候触发,一般不建议使用
  5. NativeAlloc—-当native内存有压力的时候触发
  6. Name
  7. Concurrent mark sweep—-全部对象的检测回收
  8. Concurrent partial mark sweep—-部分的检测回收
  9. Concurrent sticky mark sweep—-仅检测上次回收后创
  10. 建的对象,速度快,卡顿少,比较频繁

GC log 示例

04-06 09:41:48.541 5021-5045/com.husor.beibei I/art: Background partial concurrent mark sweep GC freed 22647(1359KB) AllocSpace objects, 5(969KB) LOS objects, 6% free, 53MB/57MB, paused 5.128ms total 68.744ms

5、如何进行内存优化

  • 1、消除内存泄漏
  • 2、使用高性能编程
  • 3、降低程序运行的内存占用

6、常见内存泄漏和高内存占用原因

  • 1、慎重使用static变量
  • 2、长周期内部类、匿名内部类长时间持有外部类引用导致相关资源无法释放(Handler或者内部线程等)
  • 3、BitMap导致内存溢出
  • 4、数据库、文件流等没有关闭
  • 5、监听器、广播注册后没有及时注销
  • 6、Adapter没有使用convertView
  • 7、字符串拼接尽量使用StringBuilder或者StringBuffer
  • 8、避免内存抖动,例如不要在onDraw中创建对象。
  • 9、界面不可见时,停止动画和相关线程
6.1、示例一:
%title插图%num
sample1.png

原因:
sBackground, 是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:Drawable->TextView->Context

解决办法:

  1. *,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
  2. 第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长
  3. 第三、使用WeakReference代替强引用。比如WeakReference<Context> mContextRef; (已经不推荐)
6.1、示例二:
%title插图%num
sample2.png

原因:

Activity退出后,其实例并未被回收。因为OneThread作为非静态内部类还持有BasicActivity的实例。

解决方案:

  1. 1、OneThread改为静态内部类
  2. 2、如果OneThread需要Context实例,使用弱引用保存它。
  3. 3、如果有必要,在BasicActivity的OnDestroy里面关闭线程

7、内存分析工具

7、1 Android Monitor
%title插图%num
util1.png

** 优点:**
使用简单,直观显示当前app的内存变化
缺点:
无法具体定位内存问题,只能给出内存笼统的变化。
常用于分析内存变化趋势。

7、2 Allocation Tracker
%title插图%num
util2.png
  1. 1、选择进程,在合适的实际开始和结束追踪。
  2. 2、Studio会自动打开文件,里面展示这一段时间内内存的分配情况,可以分析自己的程序哪里内存占用比较高,是否有大量的相同类型的 Object 被分配和释放。如果有,则其可能引起性能问题。
7、3 MAT

步骤:

  • 1生成mat文件

在android studio 生成dump文件,并转为标准dump。
如下图所示

%title插图%num
mat1.png
%title插图%num
mat2.png
  • 2使用mat分析
  • 以内部线程导致内存泄漏为例分析:
    1、多次打开该demo,生成dump文件
    2、打开MAT工具,选择“File”>>“open heap dump”,打开dump文件
    3、打开后在,“Action”里的“Histogram”,然后搜索SecondActivity
%title插图%num
mat3.png
  • 3猜测分析原因

    通过上面看到,存在11个SecondActivity的实例,有点诡异。
    选择SecondActivity这一项,然后右键“Merge Shortest Paths to GC Roots ”>>”exclude all phantom/weak/soft etc references..”

%title插图%num
mat4.png
%title插图%num
mat5.png
7、4LeakCanary 检测内存泄漏
leakcanary.png

直接可以通过通知栏看到内存泄露的地方