Android性能优化系列2---内存泄漏优化

说到内存泄漏,那什么是内存泄漏呢?

一、什么是内存泄漏

内存不在掌控之内了,当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用,从而就导致对象不能被回收。这种现象就叫内存泄漏。
而这里的主角内存,那与内存相关的知识又需要我们掌握了!真是拔出萝卜带着泥。。。
二、内存相关知识
1、介绍几个常见的数据存储的位置(总共有六个寄存器、堆、栈、方法区/静态存储区、常数存储和非RAM存储)
以下内容摘自https://www.cnblogs.com/mingziday/p/4899212.html
a、栈
栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈数据可以共享。
那些数据存放在栈中?
基本数据类型(int, short, long, byte, float, double, boolean, char)的变量存放于栈中。对象的引用存放于栈中。
栈数据共享具体指什么意思?(方法区中的常量池)
int a = 3; int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。即共享了3这个栈数据。另外,比较特殊的是String,虽然String类型不是简单的基本数据类型,但是当用String a="abc"这样的语法定义String变量的时候,会把数据存储在栈中,因此也会有上面所说的数据共享特性。java的八种基本类型(Byte Short、Integer、Long、Character、Boolean、Float、Double),除Float及Double意外,其它六种都实现了常量池,但是他们只在大于等于-128且小于等于127时才能使用常量池,如果不在此范围内,则会new一个出来,保存在堆内存中。
b、堆
堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器。堆数据在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
那些数据存放在堆中?
堆内存用于存放由new创建的对象和数组,即堆主要是用来存储对象的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
c、静态存储区
内存在程序编译的时候就已经分配 好,这块的内存在程序整个运行期间都一直存在。主要存储static声明的静态变量

2、再来了解一下Java中的引用类型吧!

这是最后一块相关知识了,客官别急,这个完了就进入正事了!
以下内容参考http://blog.csdn.net/u013256816/article/details/50907595
a、StrongReference强引用
就是指在程序代码中普遍存在的,类似Object obj = new Object()这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
强引用具备以下三个个特点:
强引用可以直接访问目标对象;
强引用锁指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常也不回收强引用所指向的对象;
强应用可能导致内存泄露;
b、SoftReference软引用
是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
对于软引用关联着的对象,如果内存充足,则垃圾回收器不会回收该对象,如果内存不够了,就会回收这些对象的内存。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
c、WeakReference弱引用
用来描述非必须的对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发送之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册引用队列中。
d、PhoatomReference虚引用
虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个持有虚引用的对象,和没有引用几乎是一样的,随时都有可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
●●● 当当当,画重点
强引用锁指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常也不回收强引用所指向的对象;
只要GC启动弱引用和虚引用就会被干掉。想详细了解引用相关知识请点击上面的链接。

我知道你已等不急要开始学习怎么查找和解决内存泄漏了,不过别急,我忘记了还有三个相关知识点要说一下!这次真不骗你!

3、虚拟机堆内存的最大值
在虚拟机中android系统给堆内存设置了一个最大值,可以通过runtime.getruntime().maxMemory()获取,现在大部分手机给每个App(不算游戏类)的最大堆内存一般是64M或128M,也就是说虽然我们的手机有4G或6G或日天的8G内存,但是你的app只能使用64M或128M或256M啊。为啥分这么点内存给我的App,因为 android系统没有虚拟内存(虚拟内存就是扩展磁盘作为内存使用)。而为了保证极端情况下,前台App和系统还能稳定运行,就得靠Low Memory Killer机制
4、Low Memory Killer
在手机剩余内存低于内存警戒线的时候,就会让Low Memory Killer在后台叉掉占用内存最多的那些App,但是如果Low Memory Killer或OOM(内存溢出)都没有叉掉你的App,好吧GC在不远处等你!
5、GC(Garbage Collection)
就是干掉那些不用的垃圾对象,频繁的GC会让你的App卡起来,所以要避免GC,就要管理好内存,减少内存泄漏。

我知道你已经饥渴难耐的想要了解查找和解决内存泄漏,接下来就满足你!

三、使用LeakCanary查找定位内存泄漏问题

内存泄漏检测工具有很多,因为这个工具检测查找问题简单唯美,我知道这就是你想要的!
github地址:https://github.com/square/leakcanary

要接入LeakCanary需要以下几步:

1、在 build.gradle 中加入依赖
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
2、在Application中初始化
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
        // Normal app init code...
    }
}
不要忘记在清单文件中指定自定义的MyApplication
 
     
android:name=".MyApplication"
经过以上步骤我们就能检测到一般的Activity的泄漏了,但还不足以针对性的监控对象的泄漏。

所以修改下MyApplication以对具体对象监控:

public class MyApplication extends Application {
    private RefWatcher refWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher= setupLeakCanary();
    }
    /*LeakCanary.install() 会返回一个预定义的 RefWatcher,同时也会启用一个 ActivityRefWatcher,
    用于自动监控调用 Activity.onDestroy() 之后泄露的 activity。*/
    private RefWatcher setupLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return RefWatcher.DISABLED;
        }
        return LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher(Context context) {
        MyApplication leakApplication = (MyApplication) context.getApplicationContext();
        return leakApplication.refWatcher;
    }
}
3、监控可能发生内存泄漏的类。
public class MainActivity extends AppCompatActivity {
    Button btn_send_msg;

    Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what==0){
                Log.i("leakTest","收到消息了");
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_send_msg=(Button) findViewById(R.id.btn_send_msg);
        btn_send_msg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handler.sendEmptyMessageDelayed(0,6000);
            }
        });

        RefWatcher refWatcher = MyApplication.getRefWatcher(this);
        refWatcher.watch(this);
    }
}
点击发送消息按钮,按返回键,过6秒后,将收到LeakCanary的通知,通知就是告诉你,你哪个类内存泄漏啦,并且有引用信息。点击通知查看详情如下图:
Android性能优化系列2---内存泄漏优化_第1张图片
点击每一个条目都可以查看详细信息。

整个详情就是一个引用链:MainActiviy的内部类Handler引用了LeakThread的this$0this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是详情最后一行所给出的MainActiviy的实例,这将会导致MainActivity无法被GC,从而产生内存泄漏。我们还可以将点击右上角的点点点将 heap dump(hprof文件)和info信息分享出去。然后转化为标准的hprof文件,就可以使用Mat进行详细分析了(不过一般用不到,前面就够了)。

下面我们修复一下Handler使用造成的内存泄漏
public class MainActivity extends AppCompatActivity {
    Button btn_send_msg;

    private NoLeakHandler handler;
    private static class NoLeakHandler extends Handler{
        private WeakReference mActivity;

        public NoLeakHandler(MainActivity activity){
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_send_msg=(Button) findViewById(R.id.btn_send_msg);
        handler=new NoLeakHandler(this);
        btn_send_msg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handler.sendEmptyMessageDelayed(0,6000);
            }
        });

        RefWatcher refWatcher = MyApplication.getRefWatcher(this);
        refWatcher.watch(this);
    }
}

再次运行,没事啦!!!哈哈

学习完了不总结一下怎么行?

四、总结
1、内存泄漏的概念?
2、内存相关知识?
3、如何使用LeakCanary检测内存泄漏?
如果以上三个问题你没有答上来,请再看一遍这个文章吧!


你可能感兴趣的:(安卓性能优化系列)