美文网首页
Android性能之内存泄漏

Android性能之内存泄漏

作者: 程序员Android1 | 来源:发表于2021-07-14 09:09 被阅读0次

    和你一起终身学习,这里是程序员 Android

    经典好文推荐,通过阅读本文,您将收获以下知识点:

    一、什么是内存泄漏?
    二、android中导致内存泄漏的主要几个点
    三、java虚拟机内存管理
    四、java内存几种分配策略?
    五、垃圾收集器是如何判断对象是否可回收?
    六、什么是内存抖动?
    七、内存抖动产生的原因?
    八、android中4种引用
    九、常见的导致内存泄漏的示例

    下面我们就以上面几个知识点来进行逐一的分析:

    一、什么是内存泄漏?

    当一个对象已经不需要在使用了,本应该被回收,而另一个正在使用的对象持有它的引用,导致对象不能被回收。因为不能被及时回收的本该被回收的内存,就产生了内存泄漏。如果内存泄漏太多会导致程序没有办法申请内存,最后出现内存溢出的错误。

    二、android中导致内存泄漏的主要几个点

    android开发中经常出现的点,我有只有了解了,才能更好的避免。

    • 使用单例模式
    • 使用匿名内部类
    • 使用异步事件处理机制Handler
    • 使用静态变量
    • 资源未关闭
    • 设置监听
    • 使用AsyncTask
    • 使用Bitmap

    上面就是我列出的几个常出现内存泄漏的几个点,下面我们将一一解读。

    三、java虚拟机内存管理

    java虚拟机内存分为虚拟机栈,本地方法栈,程序计数器,堆,方法区这几个模块,下面我们就来分析下各个模块。

    (1).虚拟机栈

    虚拟机栈主要的作用就是为执行java方法服务的,是Java方法执行的动态内存模型。会导致栈内存溢出(StackOverFlowError)

    (2).本地方法栈

    为执行native方法服务的,其他和虚拟机栈一样

    (3).程序计数器

    是当前线程执行的字节码行号指示器
    处于线程独占区
    如果是执行的是java代码,当前值为字节码指令的地址,如果是Native,值为undefined

    (4).堆

    存放对象的实例
    垃圾收集器管理的主要区域
    分代管理对象
    会导致内存溢出(OutOfMemoryError)

    (5).方法区

    存放虚拟机加载的类信息,常量,静态变量,编译后的代码和数据
    GC主要对方法区进行常量回收和类卸载
    会出现内存溢出(OutOfMemoryError)

    四、java内存几种分配策略?

    可以结合上面的内存分配模型,能很好的理解。

    (1).静态的

    静态存储区:内存在程序编译期间就已经分配完成,一般来说,这个区域在程序运行期间一直处在
    它主要储存静态数据,全局静态数据和常量

    (2).栈式的

    执行方法时,存储局部变量(编译期间,已经确定占用内存大小),操作数,动态链接,方法出口

    (3).堆式的

    也叫动态内存分配,主要存储对象实例,以及已经被加载类的Class对象(用于反射)

    五、垃圾收集器是如何判断对象是否可回收?

    我们知道内存泄漏的原因是应该被回收的对象,不能被及时回收,那么GC是如何来判断对象是否为垃圾对象呢?

    判断的方式有两个:

    • 引用计数
      对象被引用,引用计数器加1,反之减一,只有引用计数为0,那么这个对象为垃圾对象

    • 可达性
      从GCRoot节点对象开始,看是否可以访问到此对象,如果没有访问到则为垃圾对象

    可以作为GCRoot对象有以下几种:
    虚拟机栈中的局部变量
    本地方法栈中的引用对象
    方法区中的常量引用对象
    方法区中的类属性引用对象
    在native层和早期的虚拟机一般使用引用计数,但是现在的java虚拟机大多使用的是可达性。

    六、什么是内存抖动?

    堆内存都有一定的大小,能容纳的数据是有限制的,当Java堆的大小太大时,垃圾收集会启动停止堆中不再应用的对象,来释放内存。当在极短时间内分配给对象和回收对象的过程就是内存抖动。

    七、内存抖动产生的原因?

    从术语上来讲就是极短时间内分配给对象和回收对象的过程。
    一般多是在循环语句中创建临时对象,在绘制时配置大量对象或者执行动画时创建大量临时对象
    内存抖动会带来UI的卡顿,因为大量的对象创建,会很快消耗剩余内存,导致GC回收,GC会占用大量的帧绘制时间,从而导致UI卡顿,关于UI卡顿会在后面章节讲到。

    八、android中4种引用

    (1).StrongReference强引用
    从不被回收,java虚拟机停止时,才终止

    (2).SoftReference软引用
    当内存不足时,会主动回收,使用SoftReference使用结合ReferenceQueue构造有效期短

    (3).WeakReference弱引用
    每次垃圾回收时,被回收

    (4).PhatomReference虚引用
    每次垃圾回收时,被回收.结合ReferenceQueue来跟踪对象被垃圾回收器回收的活动

    九、常见的导致内存泄漏的示例

    (1).使用单例模式

        private static ComonUtil mInstance = null;
        private Context mContext = null;
    
        public ComonUtil(Context context) {
            mContext = context;
        }
    
        public static ComonUtil getInstance(Context context) {
            if (mInstance == null) {
                mInstance = new ComonUtil(context);
            }
            return mInstance;
        }
    

    使用:

    ComonUtil mComonUtil = ComonUtil.getInstance(this);
    我们看到上面的代码就是我们平时使用的单例模式,当然这里没有考虑线程安全,请忽略。当我们传递进来的是Context,那么当前对象就会持有第一次实例化的Context,如果Context是Activity对象,那么就会产生内存泄漏。因为当前对象ComonUtil是静态的,生命周期和应用是一样的,只有应用退出才会释放,导致Activity不能及时释放,带来内存泄漏。

    怎么解决呢?
    常见的有两种方式,第一就是传入ApplicationContext,第二CommonUtil中取context.getApplicationContext()。

        public ComonUtil(Context context) {
            mContext = context.getApplicationContext();
        }
    

    (2).使用非静态内部类

        /**
         * 非静态内部类
         */
        public void createNonStaticInnerClass(){
            CustomThread mCustomThread = new CustomThread();
            mCustomThread.start();
        }
    
        public class CustomThread extends Thread{
            @Override
            public void run() {
                super.run();
                while (true){
                    try {
                        Thread.sleep(5000);
                        Log.i(TAG,"CustomThread ------- 打印");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    我们就以线程为例,当Activity调用了createNonStaticInnerClass方法,然后退出当前Activity时,因为线程还在后台执行且当前线程持有Activity引用,只有等到线程执行完毕,Activitiy才能得到释放,导致内存泄漏。
    常用的解决方法有很多,第一把线程类声明为静态的类,如果要用到Activity对象,那么就作为参数传入且为WeakReference,第二在Activity的onDestroy时,停止线程的执行。

    public static class CustomThread extends Thread{
        private WeakReference<MainActivity> mActivity;
        public CustomThread(MainActivity activity){
            mActivity = new WeakReference<MainActivity>(activity)
        }
    }
    

    (3).使用异步事件处理机制Handler

        /**
         * 异步消息处理机制  -- handler机制
         */
        public void createHandler(){
            mHandler.sendEmptyMessage(0);
        }
        public Handler mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                //处理耗时操作   
                return false;
            }
        });
    

    这个应该是我们平时使用最多的一种方式,如果当handler中处理的是耗时操作,或者当前消息队列中消息很多时,那当Activity退出时,当前message中持有handler的引用,handler又持有Activity的引用,导致Activity不能及时的释放,引起内存泄漏的问题。
    解决handler引起的内存泄漏问题常用的两种方式:
    1.和上面解决Thread的方式一样,
    2.在onDestroy中调用mHandler.removeCallbacksAndMessages(null)

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

    (4).使用静态变量

    同单例引起的内存泄漏。

    (5).资源未关闭

    常见的就是数据库游标没有关闭,对象文件流没有关闭,主要记得关闭就OK了。

    (6).设置监听

    常见的是在观察者模式中出现,我们在退出Acviity时没有取消监听,导致被观察者还持有当前Activity的引用,从而引起内存泄漏。
    常见的解决方法就是在onPause中注消监听

    (7).使用AsyncTask

        public AsyncTask<Object, Object, Object> mTask = new AsyncTask<Object, Object, Object>() {
    
            @Override
            protected Object doInBackground(Object... params) {
                //耗时操作
                return null;
            }
    
            @Override
            protected void onPostExecute(Object o) {
            
            }   
        };
    

    和上面同样的道理,匿名内部类持有外部类的引用,AsyncTask耗时操作导致Activity不能及时释放,引起内存泄漏。
    解决方法同上:
    1.声明为静态类,
    2.在onPause中取消任务

    (8).使用Bitmap

    我们知道当bitmap对象没有被使用(引用),gc会回收bitmap的占用内存,当时这边的内存指的是java层的,那么本地内存的释放呢?我们可以通过调用bitmap.recycle()来释放C层上的内存,防止本地内存泄漏

    原文链接:https://www.jianshu.com/p/797395731747

    至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

    相关文章

      网友评论

          本文标题:Android性能之内存泄漏

          本文链接:https://www.haomeiwen.com/subject/lyoepltx.html