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

前言

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

但是这作为一个996福报码农,不能只挖坑,不填坑,毕竟技术债都是要还的。所以今天咱们来聊一聊Android中的内存泄漏。这篇文章总结翻译了外国友人的一篇文章:原文如下

techbeacon.com/app-dev-tes…

一、理论

先上一张图:

解释一下这张图,每个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一个内部类时,编译器会在编译时让这个内部类的实例持有外部对象。

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

上段代码:

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收集器“我”可以被回收。

上改造后的代码:

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很类似。本质都是持有外部对象的引用。

上一段很常见的代码:

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

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。它设计的是否合理,是否它应该是一个长时间存活的对象(比如单例)。

尾声

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

好了,文章到这里就结束了如果你觉得文章还算有用的话,不妨把它们推荐给你的朋友。

原文地址:https://blog.51cto.com/14332859/2446919

时间: 2024-08-30 07:03:16

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

一个优秀程序员具备的15个特性

编程是个很复杂的玩意,但是成就优秀程序员的很多因素和我们在学校中早期学到的相差无几.本文灵感来源于 Robert Fulghum 的<All I Really Need to Know I Learned in Kindergarten>. 1. 分享 尽可能地使用开源,并且如果有能力的话也可以把自己的成果分享给大家.整个社会的智慧结晶肯定比一些大公司自管自闭门造车要好. 2. 公平的心态 不要以为你的选择就是唯一能奏效的,试试其他技术.框架.方法和建议,也许其他的选择比你原先的好也未可知.要

一个优秀程序员的自我修养

对于网络推广来说,必然少不了代理IP的使用,像论坛发帖.微博推广.百度问答等等,如果你想看到效果,就需要大量操作,但是同一个IP重复操作,必然会受到限制,这个时候必须使用代理IP比如太阳IP软件的帮助,将效率和效果最大化. 当然这只是推广过程中的一小部分,如果你想更好的去操作,就要多掌握一些操作技巧,这样才能让你更好更快速的盈利. 除了用对工具.用对方法,企业对优秀的营销推广人员也是求贤若渴,每一个程序员也在不断地磨砺自我,以求在职场获得更好的待遇和更好地发展起来.一个优秀的程序员应该如何养成呢

成为一个优秀程序员的11条小贴士

我是一个充满了激情的程序员,所以我觉得我很了解程序员.在这个领域耕耘了这么多年,我和许多非常聪慧的人们接触,他们编写了具有创意的代码,但是当其他人来维护这些代码的时候,他们就很抓狂了! 能够激励程序员的最重要的一点就是他们的激情.我们对于编写良好的程序富有激情,所以我们整合了一个有11条小贴士的清单来帮助您成为一个优秀的程序员.无论您是刚开始学习程序设计还是一个有经验的开发者,有一些东西是您在参考手册上找不到的.备注:我们不说是伟大的.但,绝对是很好的,也就是说好到足够让一些程序员依赖或者使用它

一个优秀程序员必备的6个好习惯

一个伟大的程序员需要具备哪些特质呢?也许大部分人回答的是逻辑.机智.耐心和勤奋当然,其实这个问题并没有标准的答案,但是除了这些特质,习惯也是非常重要的,而这个特质可能在已经进入正轨的团队组织中才得以窥见. 除了必须的技术和逻辑思维,下面看一下在团队中应该具备怎样的好习惯吧~ 好的时间管理 亲有木有经常遇到迟到的问题,对于任何一家公司迟到都是很让人头疼的.作为一名程序员,有时候不得不熬夜加班,从而导致第二天上班迟到啦~(这点小编要投诉一下我们戴维,总是踩着点打卡,哪怕你来早那么一点点,都不会有那么

优秀程序员的七大特征(转)

世界上的很多事情都是有规律的,就像 <高效能人士的七个习惯>,优秀的程序员也有七个主要特征.这些特征有些是先天具备的,也就是天赋,但有些是在日常的编程工作中学习.进步.积累.总结获得的.每个程序员都想知道自己是不是一个优秀的程序员,那么,就参考一下下面这7条,看看自己是否达到了做一个优秀程序员的标准. 1.喜欢帮助他人,照顾比自己差的程序员 程序员的脾气通常很大,常常会和客户.同事,甚至老板在程序问题上发生争执.优秀的程序员能够站在对方的立场上想问题,能理解客户的无知.初级程序员的无能.老板的

优秀程序员的七大特征,你具备几条?

世界上的很多事情都是有规律的,就像 <高效能人士的七个习惯>,优秀的程序员也有七个主要特征.这些特征有些是先天具备的,也就是天赋,但有些是在日常的编程工作中学习.进步.积累.总结获得的.每个程序员都想知道自己是不是一个优秀的程序员,那么,就参考一下下面这 7 条,看看自己是否达到了做一个优秀程序员的标准. 1.喜欢帮助他人,照顾比自己差的程序员 程序员的脾气通常很大,常常会和客户.同事,甚至老板在程序问题上发生争执.优秀的程序员能够站在对方的立场上想问题,能理解客户的无知.初级程序员的无能.老

优秀程序员必备素质

程序员(英文Programmer)是从事程序开发.维护的专业人员.一般我们将程序员分为程序设计人员和程序编码员,但两者的界限并不非常清楚,特别是在中国. 作一个真正合格的程序员,应该具有的素质. 1:团队精神和协作能力 团队精神和协作能力是作为一个程序员应具备的最基本的素质.软件工程已经提了将近三十年了,当今的软件开发已经不是编程了,而是工程.独行侠可以写一些程序也能赚钱发财,但是进入研发团队,从事商业化和产品化的开发任务,就必须具备这种素质.可以毫不夸张的说这种素质是一个程序员乃至一个团队的安

优秀程序员因何而优秀?

这些年我曾和很多程序员一起工作,他们之中的一些人非常厉害,而另一些人显得平庸.不久前因为和一些技术非常熟练的程序员工作感觉很愉快,我花了一些时间在考虑我佩服他们什么呢?什么原因让优秀的程序员那么优秀,糟糕的程序员那么糟糕?简而言之,什么原因成就了一位优秀的程序员呢? 根据我的经验,成为一个优秀程序员同年龄,教育程度,还有和你赚多少钱没有任何关系.关键在于你的做法,更深入地说,就是你的想法.我注意到我所钦佩的程序员都有一些相似习惯.不是他们所选语言的知识,也不是对数据结构和算法的深入理解,甚至不是

一个老程序员的心理话,句句戳心(转)

一个老程序员的心理话,句句戳心 码易-猿猿-yuan 2015-11-04 12:22:45 中国的程序员是世界上最好的程序员.他们不计报酬,没日没夜地工作.没有女朋友,没有节假日,可能几年后他们一无所有.他们仍在加班. 一些人总是发出一些错误的声音,形成了劣胜优汰可怕的现象.他们在误导着中国,把我们的后继军训练成软件蓝领――――胸无大志,目光短浅,稍有点成绩就自 满就自高自大的人,浑不知天外有天,外国正在虎视眈眈盯着中国的庞大市场. 由于软件蓝领的呼声人们不再致力于培养大批的高精尖人才,掌握国