Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一个优秀程序员不可避免的问题:内存泄漏

一个优秀程序员不可避免的问题:内存泄漏

作者头像
Android技术干货分享
发布于 2019-05-19 08:43:01
发布于 2019-05-19 08:43:01
70600
代码可运行
举报
文章被收录于专栏:Android技术分享Android技术分享
运行总次数:0
代码可运行

前言

内存泄漏,一个说大不大说下不小的瑕疵。作为开发者,我们都很清楚内存泄漏是我们代码问题导致的。但是话说回来,泄漏后果会很严重嘛?这不好说,如果我们不泄漏Bitmap这种大内存的对象,那么修补内存泄漏就像鸡肋一样,“食之无味,弃之可惜”。 就比如说我们项目组,近2000w的DAU,只要不明显影响用户体验,一切以上需求为主…

一、理论

先上一张图:

解释一下这张图,每个Android(或Java)应用程序都有一个起点(GC Root),从这个点中实例化对象、调用方法。。一些对象直接引用GC Root,另一些对象又引用了这些对象。因此,形成了引用链,就像上图一样。因此垃圾收集器从GC Root开始并遍历直接或间接链接到GC Root的对象。在此过程结束时,脱离GC Root的对象/对象链将被回收。

接下来咱们再想另一个问题:

什么是内存泄漏?

有了上图,理解内存泄漏的概念就很简单,说白了就是:长生命周期对象A持有了短生命周期的对象B,那么只要A不脱离GC Root的链,那么B对象永远没有可能被回收,因此B就泄漏了。

有什么危害?

危害的话,如开篇所说。如果泄漏的内存很小,几字节,几kb….对于现在的机器性能,就像星爵打灭霸…“伤害”基本无视。但是如果泄漏的足够多,普通的GC无法回收这些泄漏的内存,那么堆将持续增加,当堆足够大的时候,就会触发“stop-the-world” GC,直接在主线程进行耗时的GC。

主线程进行耗时操作,每一个android开发者都明白这意味着什么….

所以内存泄漏足够严重,其危害还是很严重的。

二、实践

对于我们日常开发来说,有比较多的场景稍不注意就会存在内存泄漏的风险。让我们一起留意一下:

2.1、内部类Inner classes

内部类存在内存泄漏的风险,是一个老生常谈的话题。说白了就是因为我们在new一个内部类时,编译器会在编译时让这个内部类的实例持有外部对象。

这也就是,为啥我们的内部类可以引用到外部类变量、方法的原因。

上段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class BadActivity extends Activity {

    private TextView mMessageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_bad_activity);
        mMessageView = (TextView) findViewById(R.id.messageView);

        new LongRunningTask().execute();
    }

    private class LongRunningTask extends AsyncTask<Void, Void, String> {

        @Override
        protected String doInBackground(Void... params) {
            return "Am finally done!";
        }

        @Override
        protected void onPostExecute(String result) {
            mMessageView.setText(result);
        }
    }
}

大家应该都能看出这里的问题吧。作为非静态内部类的LongRunningTask,会持有BadActivity。并且LongRunningTask是一个长时间任务,也就是说,在这个任务没有完成时,BadActivity是不会被回收的,因此我们的BadActivity就被泄漏了。那么怎么改呢?

解决原理

首先我不能让LongRunningTask持有BadActivity。那么我们需要使用静态内部类(static class)。这样的确不会持有BadActivity,但是问题来了,我们LongRunningTask不持有BadActivity,也就意味着没办法引用到BadActivity中的变量,那么我们的更新UI的操作就做不了,也就是说还是要显示的传一个BadActivity中我们需要的变量进来…但是这样有造成了同样的泄漏问题。

因此,我们需要对传入的变量使用WeakReference进行包一层。但发生GC的时候,告诉GC收集器“我”可以被回收。

上改造后的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class GoodActivity extends Activity {

    private AsyncTask mLongRunningTask;
    private TextView mMessageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_good_activity);
        mMessageView = (TextView) findViewById(R.id.messageView);

        mLongRunningTask = new LongRunningTask(mMessageView).execute();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mLongRunningTask.cancel(true);
    }

    private static class LongRunningTask extends AsyncTask<Void, Void, String> {

        private final WeakReference<TextView> messageViewReference;

        public LongRunningTask(TextView messageView) {
            this.messageViewReference = new WeakReference<>(messageView);
        }

        @Override
        protected String doInBackground(Void... params) {
            String message = null;
            if (!isCancelled()) {
                message = "I am finally done!";
            }
            return message;
        }

        @Override
        protected void onPostExecute(String result) {
            TextView view = messageViewReference.get();
            if (view != null) {
                view.setText(result);
            }
        }
    }
}

2.2、匿名类 Anonymous classes

这一类和2.1很类似。本质都是持有外部对象的引用。

上一段很常见的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MoviesActivity extends Activity {

    private TextView mNoOfMoviesThisWeek;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_movies_activity);
        mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);

        MoviesRepository repository = ((MoviesApp) getApplication()).getRepository();
        repository.getMoviesThisWeek()
                .enqueue(new Callback<List<Movie>>() {

                    @Override
                    public void onResponse(Call<List<Movie>> call,
                                           Response<List<Movie>> response) {
                        int numberOfMovies = response.body().size();
                        mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));
                    }

                    @Override
                    public void onFailure(Call<List<Movie>> call, Throwable t) {
                        // Oops.
                    }
                });
    }
}

2.3、注册Listener

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SingleInstance.setMemoryLeakListener(new OnMemoryLeakListener(){
    //…..
})

这里写了段很常见的伪码,一个单例的对象,register了一个Listener,并且这个Listener被单例的一个成员变量引用。

OK,那么问题很明显了。单例作为静态变量,肯定是一直存在的。而其内部持有了Listener,而Listener作为一个匿名类,有持有了外部对象的引用。因此这条GC链上的所有对象都不会被释放。

解决也很简单,适当的时机,在单例中将Listener的引用置为null。这样,Listener和单例之间的引用关系断了,Listener链上的所有内容就可以被正常释放掉了。也就是咱们常做的在onDestory()进行unRegisterListener的操作。

类似不注意的内容,还包括Lambda。不过有一点值得注意的,在Kotlin的Lambda中,如果我们没有使用外部对象的变量或者方法,那么Kotlin在编译时,这个Lambda是不会持有外部对象的引用的。也算是Kotlin的一些优化吧

2.4、Contexts

上下文的滥用,也是泄漏的大客户。不过大家针对这类问题应该比较熟悉。

比如:长时间存活的对象,不建议持有Activity的context,而是使用ApplicationContext。如果ApplicationContext没办法完成业务,那么就需要好好考虑一下:这个长时间存活的对象,为什么必须要持有Activity的context。它设计的是否合理,是否它应该是一个长时间存活的对象(比如单例)。

尾声

关于内存泄漏,还是需要咱们平时多注意,对自己写的每一行代码都多思考。毕竟这东西“不是病,但疼起来真要命”。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019.05.17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
内存泄漏三问—vivo真题
说到性能优化,就不得不提下内存泄漏了,内存泄漏发生的原因以及解决办法你是否都已了解呢?看看今天的三问:
码上积木
2020/09/27
6270
Android内存泄漏终极解决篇(下)
一、概述 在 Android内存泄漏终极解决篇(上)中我们介绍了如何检查一个App是否存在内存泄漏的问题,本篇将总结典型的内存泄漏的代码,并给出对应的解决方案。内存泄漏的主要问题可以分为以下几种类型: 静态变量引起的内存泄漏 非静态内部类引起的内存泄漏 资源未关闭引起的内存泄漏 二、静态变量引起的内存泄漏 在java中静态变量的生命周期是在类加载时开始,类卸载时结束。换句话说,在android中其生命周期是在进程启动时开始,进程死亡时结束。所以在程序的运行期间,如果进程没有被杀死,静态变量就会一直存在,不会
非著名程序员
2018/02/02
7710
最常见的Android内存优化方式及防止泄漏造成OOM总结篇
内存优化目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。既然说到内存泄漏和优化,就不得不先简单了解一下内存分配策略,然后再举常见泄漏例子和解决方法,最后做一下总结,这样更直观全面了解Android内存方面处理。
Android技术干货分享
2019/10/08
1.3K0
Android内存泄漏分析
强引用:类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
用户1205080
2019/03/06
1.6K0
Android内存泄漏分析
看完这篇文章,解决 APP 中 90 % 的内存异常问题
在 Android 中我们写的 .java 文件,最终会编译成 .class 文件, class 又由类装载器加载后,在 JVM 中会形成一份描述 class 结构的元信息对象,通过该元信息对象可以知道 class 的结构信息 (构造函数、属性、方法)等。
Android技术干货分享
2019/08/08
8200
Android经典面试题之开发中常见的内存泄漏,以及如何避免和防范
内存泄漏(Memory Leak)在软件开发中指的是程序未能释放已不再需要的内存,从而导致内存的浪费。在Android应用开发中,内存泄漏尤其重要,因为移动设备通常内存较为有限,长期的内存泄漏会导致应用变慢、崩溃,甚至影响整个系统的稳定性。
AntDream
2024/08/06
1860
Android经典面试题之开发中常见的内存泄漏,以及如何避免和防范
Android开发之漫漫长途 番外篇——内存泄漏分析与解决
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。
LoveWFan
2018/08/07
4700
Android开发之漫漫长途 番外篇——内存泄漏分析与解决
Android开发:详解Handler的内存泄露
上面提到,在Java里,非静态内部类和匿名类都会潜在的引用它们所属的外部类。 但是,静态内部类不会。
Carson.Ho
2019/02/22
1.2K1
Android面试每日一题: Handler 如何有效地避免内存泄漏问题
在Android系统中,Handler是一个消息发送和处理机制的核心组件之一,与之配套的其他主要组件还有Looper和Message,MessageQueue。 Message和Runnable类是消息的载体。MessageQueue是消息等待的队列。Looper则负责从队列中取消息。
老马的编程之旅
2022/09/21
8861
Android面试每日一题: Handler 如何有效地避免内存泄漏问题
Android 性能优化最佳实践
快,稳,省,小,这四点很形象的代表了性能的四个方面,同时也让我们知道我们 App 现在是否是款性能良好的 APP,如果有一项不达标,那么说明我们的应用有待优化。
李林LiLin
2020/11/22
1.5K0
Handler引起的内存泄漏及解决方法
当使用内部类或匿名内部类的方式创建Handler时,Handler对象会隐式地持有一个外部类对象的引用(这里的外部类是Activity)。一般在一个耗时任务中会开启一个子线程,如网络请求或文件读写操作,我们会使用到Handler对象。但是,如果在任务未执行完时,Activity被关闭了,Activity已不再使用,此时由GC来回收掉Activity对象。由于子线程未执行完毕,子线程持有Handler的引用,而Handler又持有Activity的引用,这样直接导致Activity对象无法被GC回收,即出现内存泄漏。
程思扬
2022/01/10
7890
重谈Handler的内存泄漏
在多线程操作中,handler会使用的非常多,但是每次使用handler你有没有考虑内存泄漏的问题。
Yif
2019/12/26
1.2K0
Android 进阶解密笔记-热修复
内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
Yif
2019/12/26
4530
Android性能优化之内存泄漏,你想要的这里都有~
在Android中,内存泄露的现象十分常见;而内存泄露导致的后果会使得应用Crash 本文 全面介绍了内存泄露的本质、原因 & 解决方案,最终提供一些常见的内存泄露分析工具,希望你们会喜欢。
用户9239674
2022/02/09
9450
Android 内存泄露简介、典型情景及检测解决
什么是内存泄露? Android虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,导致不能及时回收这个对象所占用的内存。内存泄露积累超过Dalvik堆大小,就会发生OOM(OutOfMemory)。 内存泄露的经典场景 非静态
非著名程序员
2018/02/02
8420
Android 内存泄露简介、典型情景及检测解决
android学习笔记----Handler的使用、内存泄漏、源码分析等一系列问题
学习路径,先看慕课网视频:https://www.imooc.com/learn/267
砖业洋__
2023/05/06
6580
android学习笔记----Handler的使用、内存泄漏、源码分析等一系列问题
Android 内存泄漏总结
内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。最近自己阅读了大量相关的文档资料,打算做个 总结 沉淀下来跟大家一起分享和学习,也给自己一个警示,以后 coding 时怎么避免这些情况,提高应用的体验和质量。
阳仔
2019/07/31
6770
Android 内存泄漏总结
Android开发从GC root分析内存泄漏
我们常说的垃圾回收机制中会提到GC Roots这个词,也就是Java虚拟机中所有引用的根对象。我们都知道,垃圾回收器不会回收GC Roots以及那些被它们间接引用的对象。但是,对于GC Roots的定义却不是很清楚。它们都包括哪些对象呢?
yuxiaofei93
2018/09/11
2.3K0
[译]Android防止内存泄漏的八种方法(下)
在上一篇Android内存泄漏的八种可能(上)中,我们讨论了八种容易发生内存泄漏的代码。其中,尤其严重的是泄漏Activity对象,因为它占用了大量系统内存。不管内存泄漏的代码表现形式如何,其核心问题在于:
用户2802329
2018/08/07
5020
Android 关于内存泄露,你必须了解的东西
内存管理的目的就是让我们在开发过程中有效避免我们的应用程序出现内存泄露的问题。内存泄露相信大家都不陌生,我们可以这样理解:「没有用的对象无法回收的现象就是内存泄露」。
developerHaoz
2018/08/20
1.2K0
相关推荐
内存泄漏三问—vivo真题
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验