Andriod内存

来源:互联网 发布:中世纪2mod原版优化9 编辑:程序博客网 时间:2024/06/05 19:05

Android内存分析

内存管理机制

  • 虚拟机有两种运行方式(Delvik和ART)

Java对象的生命周期

    1. 创建阶段(Created)    2. 应用阶段(InUse)    3. 不可见阶段(Invisible):线程栈中的临时变量    4. 不可达阶段(Unreachable):该对象不再被任何强引用持有    5. 收集阶段(Collected)    6. 终结阶段(Finalized)    7. 对象空间重新分配阶段(Deallocated)

内存回收机制

  • 内存
    1. 年亲代(Eden,S0,S1(Survivior))(复制算法):扫描出被引用的对象将其复制到一快新的区域
    2. 年老代(Mark算法),:扫描出存活的对象,然后在回收未被标记的对象
    3. 持久代(静态类或者方法):用于存放静态的类和方法
  • 回收步骤:
    1. 当new一个对象的时候回首先加入到Eden区,
    2. 当Eden区满的时候GC以后依然存在复制到S0区中;
    3. 当S0区满时,GC后依然存在的将复制到S1区中后清空S0,以后从Eden区来的对象将自己复制到S1区,
    4. 当S1区满GC后会将重S0复制过来的依然存在的复制到年老代,
    5. 当这些对象在年老区存在一定时间后,会移动到年老代(内存溢出后的对象就是保存在这个区,一直引用,不易回收)
  • 内存种类:

    • stop world:在分配内存不够的情况下引发GC:运行GC其他线程停止,包括UI线程,直到GC停止,如果GC的时间超过16ms会导致丢帧,由disPlay渲染,CPU,GPU的双缓冲机制可能会有卡顿或者延迟加载等
    • 当内存达到一定的阈值时触发GC,此时不会调取stop world(程序正常运行)
    • 显示的调用GC:System.gc
  • 内存泄漏导致OOM的原因:

    • 当一个对象创建后不再使用,但是是可达的垃圾回收器不会将其辨认出来,仍然保留在内存中并最终会到年老代,因此当更多的内存泄漏以后,运行内存不足的情况,当在此分配空间就会导致不断的GC操作可能导致卡顿,但是却不会释放这些内存对象,最终OOM;
    • GC机制原理:选择一些还存在的内存作为GC Roots,通过可达性来判断是否需要回收;

内存泄露的概念

内存泄漏(memory leak)

  • 是指由于疏忽或错误造成程序未能释放已经不再使用的内存。那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况。在应用中内出现一次两次的内存泄漏获取不会出现什么影响,但是在应用长时间使用以后,若是存在大量的Activity无法被GC回收的话,最终会导致OOM的出现。
  • 内存泄露的种类:
    1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
    2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
    3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
    4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
  • 从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到.

导致内存泄露的因素

一.静态Activity和View(使用静态时需要注意)

这里写图片描述
比如这种情形就会产生一旦程序调取mainActivity的时候就会产生内存泄露

二.Thread,匿名类,内部类

  • 在下面这段代码中存在一个非静态的匿名类对象Thread,会隐式持有一个外部类的引用LeakActivity,从而导致内存泄漏。同理,若是这个Thread作为LeakActivity的内部类而不是匿名内部类,他同样会持有外部类的引用而导致内存泄漏。在这里只需要将为Thread匿名类定义成静态的内部类即可(静态的内部类不会持有外部类的一个隐式引用)。
public class LeakActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_leak);        leakFun();    }    private void leakFun(){        new Thread(new Runnable() {            @Override            public void run() {                try {                    Thread.sleep(Integer.MAX_VALUE);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        });    }}

三.动画

  • 在属性动画中有一类无限循环动画,如果在Activity中播放这类动画并且在onDestroy中去停止动画,那么这个动画将会一直播放下去,这时候Activity会被View所持有,从而导致Activity无法被释放。解决此类问题则是需要早Activity中onDestroy去去调用objectAnimator.cancel()来停止动画。
public class LeakActivity extends AppCompatActivity {    private TextView textView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_leak);        textView = (TextView)findViewById(R.id.text_view);        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);        objectAnimator.start();    }}

四.Handler以及线程导致

  • 大多时候我们使用handler的时候,并没有进行特殊处理,这就为我们的app埋下了内存泄露的隐患。我们先来理解一下为什么会出现这个问题。我们都知道handler有两个小伙伴MessageQueue和Looper,一个是消息队列,handler所有的sendMessage的消息都会存储在这里,Looper是循环体,它会不停地从MessageQueue中将消息发送给handler,最终在handler的handleMessage中进行处理。如果MessageQueue中一直有message没有被处理,那么hander就会一直存在,因为他是一个内部类,它就会自动引用Activity,导致Activity不能够被正常回收。

我们看一下AndroidStudio在我们使用handler时给我们的提示

Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

大概意思就是:

一旦Handler被声明为内部类,那么可能导致它的外部类不能够被垃圾回收。如果Handler是在其他线程(我们通常成为worker thread)使用Looper或MessageQueue(消息队列),而不是main线程(UI线程),那么就没有这个问题。如果Handler使用Looper或MessageQueue在主线程(main thread),你需要对Handler的声明做如下修改:
声明Handler为static类;在外部类中实例化一个外部类的WeakReference(弱引用)并且在Handler初始化时传入这个对象给你的Handler;将所有引用的外部类成员使用WeakReference对象。

我们的解决方法是:
1.将Handler变为静态内部类。
2.将传递给来的Activity变为WeakReference

public class TestActivity extends Activity {    private StaticHandler mStaticHandler;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mStaticHandler = new StaticHandler(this);    }    static class StaticHandler extends Handler {        WeakReference<Activity> activity;        public StaticHandler(Activity activity) {            this.activity = new WeakReference<Activity>(activity);        }        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);        }    }}
  • Thread造成内存泄露的愿意在于thread的不可控性,因为不清楚thread什么时候执行完,假如清理Activity
    时thread还没有执行完,那么Activity就不能够被销毁,这就出现了内存泄露。其实thread的的解决方法和我们前面解决Handler是一样的。

五.第三方库使用不当

  • 如RxJava,eventBus等一些第三开源框架的使用,若是在Activity销毁之前没有进行解除订阅将会导致内存泄漏。

六..静态集合中对象没清理造成的内存泄露

  • 像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
Static Vector v = new Vector(10); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }

在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。

七.各种连接

  • 比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。

总结内存泄漏

  1. 我们用尽量使用Application的Context来替代Activity的Context,因为Application和应用的生命周期相同。

  2. 资源操作一定要记得关闭,做好善后工作。

  3. 使用handler,thread要变成静态内部类和传递weakReference。

  4. 注意static变量不要直接或者间接的引用Activity。

  5. 静态集合使用要记得最后移除里面的引用。

  6. 使用属性动画要在onDestroy中关闭动画。

使用MAT检测没存泄露

对于常见的内存泄露进行介绍完以后,在这里再看一下使用MAT(Memory Analysis Tool)来检测内存泄露。MAT下载链接: http://www.eclipse.org/mat/downloads.php

MAT的studio的使用

1.打开AndroidStudio中的Monitors可以看到如下界面
*可以看到ViewPager依赖的MainActivity被static修饰了,所以会导致内存泄露

/** * Created by shiqiang on 2017/1/27. * * Viewpager + Fragment情况下,fragment的生命周期因Viewpager的缓存机制而失去了具体意义 * 该抽象类自定义一个新的回调方法,当fragment可见状态改变时会触发的回调方法,介绍看下面 * * @see #onFragmentVisibleChange(boolean) */public abstract class ViewPagerFragment extends Fragment {    public static MainActivity mainActivity;

这里写图片描述

  1. 那么现在再点击Dump Java Heap按钮,在captures窗口看到生成得hprof文件。
    这里写图片描述

3.studio上面captures上面会生成.hprof文件,导出到本地
这里写图片描述

4.使用MAT分析内存,FIle->open File将刚刚保存的文件打开分析
这里写图片描述
点击按钮调取Histogram界面
5.搜索Activity分析每个Activity的引用情况
这里写图片描述
6.点击鼠标右键选择Merge Shortest paths to GC Roots并选择exclude weak/soft references来排除弱引用和软引用。
这里写图片描述

7.可以看到是由于刚刚的ViewpagerFragment所导致的
这里写图片描述
8.找到原因改善以后再运行时的内存
这里写图片描述

2 0
原创粉丝点击