Android性能优化之--内存泄漏

来源:互联网 发布:淘宝人工 编辑:程序博客网 时间:2024/06/06 14:12

性能优化之内存泄漏

在Android开发中,内存泄漏是开发过程中时刻要注意的问题,下面我们就来学习下什么叫内存泄漏,为什么会出现内存泄漏,怎样去解决?

一、Android为应用内存分配机制

首先介绍一下Android的内存机制。在Android的运行机制中,不用的分辨率不同Ram大小的设备会为每个应用分配不同的初始化内存。高分辨率的设备比低分辨率的设备分配更多内存。这个可以在自己的设备根目录system/build.prop文件看到。以下是测试机 乐视LX620的内存分配信息:

dalvik.vm.heapstartsize=8m —-起始分配内存
dalvik.vm.heapgrowthlimit=512m —- 一般情况app申请的最大内存
dalvik.vm.heapsize=512m —- 设置largeheap时,App可用的最大内存
dalvik.vm.heaptargetutilization=0.75 —- GC相关
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m —– GC机制相关

代码:

public void memoryInfo() {    Runtime rt = Runtime.getRuntime();    long maxMemory = rt.maxMemory();    Log.d("===maxMemory",Long.toString(maxMemory/(1024*1024)));    ActivityManager manager = (ActivityManager)        getSystemService(Context.ACTIVITY_SERVICE);    Log.d("===normalMemory",Long.toString(manager.getMemoryClass()));    Log.d("===largeMemory",Long.toString(manager.getLargeMemoryClass()));}

结果:

09-23 16:38:13.831 3930-3930/com.zengke.memory.memorydemo D/===maxMemory: 25609-23 16:38:13.831 3930-3930/com.zengke.memory.memorydemo D/===normalMemory: 25609-23 16:38:13.831 3930-3930/com.zengke.memory.memorydemo D/===largeMemory: 512

把AndroidManifest.xml中的application标签加上:

<application        ...        android:largeHeap="true"        ...>        ...</application>

maxMemory的值就可以达到largeMemory。但是不建议这样做,因为如果所有app都这样做的话,那样各个抢着占用手机内存,后果可想而知,所有最根本的方法就是自己有效控制好应用的内存分配。


二、再来说下java的GC垃圾回收机制

一般垃圾回收机制一般做两件基本事情:

  1. 发现无用的信息对象;
  2. 回收将无用对象占用的内存空间,使该空间可被程序再次利用;

它的原理就是:把所有的引用关系看做一张图,从这张图的GC root节点,开始寻找对应的引用节点, 找到这个节点后,再去寻找这个节点的引用节点。有点类似于顺藤摸瓜。如果有个节点是不可达的,即这个节点是不被引用的,也就是我们所回收的垃圾。

通过引用链无法访问某个对象的时候,该对象即被回收。


三、再来看看java内存分配的几种策略

  1. 静态的:
    静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序的整个运行期间都一直存在。它主要存放静态数据,全局的static数据和一些常量;

  2. 栈式的:
    在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上创建,函数执行结束的时候这些存储单元就会自动释放掉。栈内存分配的运算速度很快,因为内置在处理器里面,不过容量有限;

  3. 堆式的:
    也叫动态分配内存,有时候可以用new来申请分配一个内存,在c/c++可能需要自己负责释放(java里面直接依赖GC垃圾回收机制) 。在c/c++里可以自己掌控内存,而java在这方面没有很好的方法解决垃圾内存,需要编程的时候注意自己的良好编程习惯。

区别
堆是不连续的内存区域,对空间比较灵活也特别大;
栈是一块连续的内存区域,大小有操作系统决定。
堆管理很麻烦,频繁地new/remove会造成大量的内存碎片这样就会慢慢导致效率低下。
对于栈的话,他先进后出,进出完全不会产生碎片。运行效率高切稳定。

public class Main{    int a=1; // a在堆中    Student s1= new Student();  // s1在堆中    public void XXX(){        int b = 1; // 栈里面        Student s2 = new Student();  //s2在堆中     }}

总结:

  1. 成员变量全部存储在堆中(包括基本数据类型,引用和引用的对象实体)–因为他们属于类,类对象最终还是要被new出来的;
  2. 局部变量的基本数据类型和引用存储在栈当中,引用的对象实体存储在堆中(就比图s2 指向的new student())—-因为他们属于方法中的变量,生命周期会随着方法一起结束。

我们所讨论的内存泄漏,主要讨论堆内存它存放的就是引用指向的对象实体。


四、下面再来说下Java的引用数据类型

StrongReference 强引用:

回收时机:从不回收
使用:对象的一般保存
生命周期:JVM停止的时候才会停止

SoftReference: 软引用

回收时机:当内存不足的时候
使用:SoftReference 结合Reference构造有效期短;
生命周期: 内存不足时终止;

WeakReference : 弱引用:

回收时机:在垃圾回收的时候;
使用:同软引用;
生命周期:GC时终止

PhatomReference 虚引用:

回收时机:在垃圾回收的时候;
使用:和Reference来跟踪对象被垃圾回收期间回收的活动;
生命周期:GC后终止;

开发时,为了防止内存溢出,处理一些比较占用内存大并且生命周期长的对象的时候,可以尽量使用软引用和弱引用;


五、终于到了—– 内存泄漏:

What?

什么是内存泄漏:内存不在GC掌控之内了;
当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致对象不能被回收。这种到之类本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏。
如果不停发生内存泄漏,应用的占用内存越来越大,最后达到可分配内存最大值,出现内存不足情况,最终导致OOM的发生。

Why?

下面我们来看下内存泄漏的情景:



1. Activity中的handler长期持有activity引用导致activity泄漏:

public class MainActivity extends AppCompatActivity {    private final Handler myHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            //doSomething        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        myHandler.postDelayed(new Runnable() {            @Override            public void run() {                //doSomething            }        },60*10*1000);    }}

原因:由于myHandler延时10分钟就会发送一条消息,当activity finish之后,延时发送的消息会在主线程的消息队列中存货十分钟知道被looper拿出然后给到handler处理。此消息隐式持有外部类handler的引用,而handler有隐式的持有外部类activity的引用,直到消息处理完,这个引用都不会被稀放,因此activity即使finish,但仍然不会被gc回收。
引用的顺序为:MessageQueue –> Message –> Runnable –> Handler – > Activity,从这个
引用链得到Activity与MessageQueue关联,所以Activity不能回gc回收。

解决方式:为了解决Handler隐式的持有外部类引用,我们应当将Handler定义在一个新文件或在Activity中使用静态内部类。因为静态内部类不会持有外部类的引用,这样当activity finish时,handler不会持有activity内存泄漏。如果需要在Handler内部调用外部Activity的方法,正确做法是让Handler持有外部Activity的弱引用(WeakReference),这样当gc扫描的时候,这个弱引用的对象就会被回收。

public class MainActivity extends AppCompatActivity {    private final MyHandler mHandler = new MyHandler(this);    private static Runnable sRunnable = new Runnable() {        @Override        public void run() {            //doSomething        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mHandler.postDelayed(sRunnable, 60 * 10);        this.finish();    }    private static class MyHandler extends Handler {        private final WeakReference<MainActivity> mActivity;        public MyHandler(MainActivity activity) {              this.mActivity = new WeakReference<MainActivity>(activity);        }         @Override         public void handleMessage(Message msg) {              MainActivity activity = mActivity.get();              if (activity != null) {                //doSomething         }       }    }}

也可以在onDestory方法中干掉handler中所有的callback和message:

    @Override    protected void onDestroy() {        super.onDestroy();        mHandler.removeCallbacksAndMessages(null);    }


2、非静态匿名内部类造成内存泄漏

在Activity中开线程时,直接newThread(Runnable)就可以了。但是如果Activity结束了,而Thread还在跑,就会导致内存泄漏的发生。这是因为new Thread作为静态内部类对象都会隐式持有一个外部类对象的引用,我们所创建的线程就是一Activity中的一个内部类,持有Activity对象的引用,所有当Activity结束了,而子线程还在跑就会导致Activity内存泄漏。如下:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        testThread();    }    private void testThread() {        new Thread(new Runnable() {            @Override            public void run() {                while (true){                    SystemClock.sleep(1000);                }            }        }).start();    }}

new Thread()是匿名内部类,且非静态的。所以会隐式持有外部类的一个引用,只要非静态匿名类对象没有被回收,Acitivity就不会被回收。

解决方法:
同样把Thread定义为静态的内部类,这样就不会持有外部类的引用了。



3、单例的错误引用

CommonUtil :

public  class CommonUtil {    private Context mContext;    private static CommonUtil instance;    private CommonUtil(Context context) {        mContext = context;    }    public static CommonUtil getInstance(Context context) {        if (instance== null)            instance= new CommonUtil(context);        return instance;    }}

Activity:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        CommonUtil instance= CommonUtil.getInstance(this);    }

上面代码,在Activity中通过单例实例化CommonUtil时,传入了Activity对象,这个CommonUtil的实例化instance便引用了Activity对象,而instance是单例的,生命周期和整个app一样,所以当该Activity结束时,因为被引用而导致无法进行回收。



4、单例 + 依赖注入:

首先看下一下代码:

LeakActivity:

public class LeakActivity extends AppCompatActivity {    private TestManager testManager = TestManager.getInstance();    private MyListener listener=new MyListener() {        @Override        public void doSomeThing() {}    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        testManager.registerListener(listener);    }}

TestManager.java:

public class TestManager {    private static final TestManager INSTANCE = new TestManager();    private MyListener listener;    public static TestManager getInstance() {        return INSTANCE;    }    public void registerListener(MyListener listener) {        this.listener = listener;    }    public void unregisterListener() {        listener = null;    }}interface MyListener {    void doSomeThing();}

在LeakActivity中TestManager.getInstance()创建对象实例。TestManager中采用单例模式返回一个TestManager实例变量。
TestManager中的实例变量是static静态变量,静态变量和类的生命周期是一样的。类加载的时候,静态变量就被加载,类销毁时,静态变量就被销毁。
因为INSTANCE是一个单例,所以和app的生命周期是一样的。当app进程销毁时,堆内存的INSTANCE对象才会被释放,INSTANCE的生命周期是非常长的。
而又可以看到代码中的Activity里创建了listener非静态内部类,所以listener就持有外部类Activity的引用。随着testManager的registerListener(listener)执行,TestManager的listener就持有了Activity的引用,由此形成了一个引用链。
引用链为:TestManager–> listener –> Activity
所以解决方式:要在Activity的onDestory()方法里注销注册的listener

@Override    protected void onDestroy() {        testManager.unregisterListener();        super.onDestroy();    }

将TestManager中的listener与Activity的断开。

参考:

http://blog.csdn.net/jkerving/article/details/52246851

原创粉丝点击