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

image




引言

1. Android性能优化篇之内存优化--内存泄漏

2.Android性能优化篇之内存优化--内存优化分析工具

3.Android性能优化篇之UI渲染性能优化

4.Android性能优化篇之计算性能优化

5.Android性能优化篇之电量优化(1)——电量消耗分析

6.Android性能优化篇之电量优化(2)

7.Android性能优化篇之网络优化

8.Android性能优化篇之Bitmap优化

9.Android性能优化篇之图片压缩优化

10.Android性能优化篇之多线程并发优化

11.Android性能优化篇之数据传输效率优化

12.Android性能优化篇之程序启动时间性能优化

13.Android性能优化篇之安装包性能优化

14.Android性能优化篇之服务优化

介绍

今天主要是讲内存泄漏的产生原因分析,常见的导致内存泄漏的示例,以及内存泄漏优化的方法。中间穿插着有关java虚拟机内存管理,内存分配策略,垃圾收集器的相关知识点。下面就来列出今天讲解的大体流程。

讲解流程:

1.什么是内存泄漏?
2.android中导致内存泄漏的主要几个点
3.java虚拟机内存管理
4.java内存几种分配策略?
5.垃圾收集器是如何判断对象是否可回收?
6.什么是内存抖动?
7.内存抖动产生的原因?
8.android中4种引用
9.常见的导致内存泄漏的示例

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


1.什么是内存泄漏?

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


2.android中导致内存泄漏的主要几个点

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

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

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


3.java虚拟机内存管理

img_1_1.png

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

(1).虚拟机栈
  • 虚拟机栈主要的作用就是为执行java方法服务的,是Java方法执行的动态内存模型。
* 会导致栈内存溢出(StackOverFlowError)
(2).本地方法栈
  • 为执行native方法服务的,其他和虚拟机栈一样
(3).程序计数器
  • 是当前线程执行的字节码行号指示器
  • 处于线程独占区
  • 如果是执行的是java代码,当前值为字节码指令的地址,如果是Native,值为undefined
(4).堆
  • 存放对象的实例
  • 垃圾收集器管理的主要区域
  • 分代管理对象
  • 会导致内存溢出(OutOfMemoryError)
(5).方法区
  • 存放虚拟机加载的类信息,常量,静态变量,编译后的代码和数据
  • GC主要对方法区进行常量回收和类卸载
  • 会出现内存溢出(OutOfMemoryError)


4.java内存几种分配策略?

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

(1).静态的
  • 静态存储区:内存在程序编译期间就已经分配完成,一般来说,这个区域在程序运行期间一直处在
  • 它主要储存静态数据,全局静态数据和常量
(2).栈式的
  • 执行方法时,存储局部变量(编译期间,已经确定占用内存大小),操作数,动态链接,方法出口
(3).堆式的
  • 也叫动态内存分配,主要存储对象实例,以及已经被加载类的Class对象(用于反射)


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

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

判断的方式有两个:

* 引用计数

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

* 可达性

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

可以作为GCRoot对象有以下几种:

  • 虚拟机栈中的局部变量
  • 本地方法栈中的引用对象
  • 方法区中的常量引用对象
  • 方法区中的类属性引用对象

在native层和早期的虚拟机一般使用引用计数,但是现在的java虚拟机大多使用的是可达性。


6.什么是内存抖动?

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

7.内存抖动产生的原因?

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


8.android中4种引用

(1).StrongReference强引用

从不被回收,java虚拟机停止时,才终止

(2).SoftReference软引用

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

(3).WeakReference弱引用

每次垃圾回收时,被回收

(4).PhatomReference虚引用

每次垃圾回收时,被回收.结合ReferenceQueue来跟踪对象被垃圾回收器回收的活动


9.常见的导致内存泄漏的示例

(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层上的内存,防止本地内存泄漏


上面就是这章要将的所有内容,常见的内存泄漏的例子也看了,但是我们怎么知道是否内存泄漏了呢,或者说怎么去发现我们的项目中有内存泄漏呢?下一章就来结合上面的例子通过内存分析工具来具体的分析。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容