终极理解弱引用在Android和Java中的工作原理[译]

几周前我有幸参加了在波兰举行的国际移动会议,这是移动开发者最好的会议之一。在“最佳实践”系列演讲中,我的朋友兼同事Jorge Barroso 的一个观点引起了我的注意:

如果你是一个Android开发者,但是你没有使用弱引用,那么你会有麻烦。

刚好,几个月前我和Diego Grancini合作出版了我的上一本书,“Android High Performance”。其中最热门的一章就是讨论Android中的内存管理。在这一章中,我们讨论了在移动设备上内存是怎么工作的,内存泄漏是怎么发生的,内存泄漏这个问题为什么如此重要以及我们需要采取什么技术来避免。自从我从事Android开发以来,我注意到一种倾向,凡是和内存泄漏或者内存管理有关的事情,开发者总是不由自主的回避或者降低其优先级。如果功能需求已经满足,为什么要自寻烦恼?我们总是急于开发新的功能,我们宁愿在我们的下一个Sprint演示中呈现一些视觉效果,而不是关心那些人们不会第一眼就发现的问题。

一个不错的观点就是,这将不可避免的导致技术债务。我甚至还补充一点,技术债务在现实世界也会产生一些影响,这些影响我们无法通过单元测试来衡量:失望、开发者之间扯皮、软件交付质量低下以及工作激情消失。这个影响很难衡量的原因是它们常常发生在未来的某一个时间点。它的发生有一点像政客:如果我仅仅当政8年,我为什么要为第12年发生的事情烦恼?与之不同的是软件开发在飞速发展。

如果要写适用于软件开发的思维模式,需要很长的篇幅,并且已经有很多书和文章可供您探索,然而简单的介绍内存引用的不同类型,它们各自的含义以及怎样把它们应用到Android开发中则容易的多,这就是我在本文中要做得事情。

首先:Java中什么是引用?

引用指向一个已经声明的对象,你可以访问它。

Java默认有4种引用:强引用软引用弱引用虚引用。也有一些人认为只有两种引用,强引用和弱引用,并且弱引用可以表现为两种形式。在生活中,我们倾向于将一切东西像植物学家一样进行分类。不管哪一种分类更适合你,首先你需要理解他们。然后你才能提出你自己的分类方式。

每一种引用的含义是什么?

强引用:强引用是Java中最常见的引用。每当我们创建一个新的对象,默认就创建了一个强引用。比如,但我们写下如下代码:

MyObject object = new MyObject();

当一个MyObject类型的对象被创建,object就持有一个它的强引用。到目前为止,还是比较简单的,你还能跟上吗?那么,接下来会发生更多有趣的事情。这个Object强可达的-也就是说它可以通过一个强引用链到达。这将阻止垃圾回收器回收并且销毁它,我们最希望的是垃圾回收器及时的回收并销毁它。但是现在让我们一起来看一个例子,这将我和我们想要的不一样。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        new MyAsyncTask().execute();
    }
    
    private class MyAsyncTask extends AsyncTask {
        @Override
        protected Object doInBackground(Object[] params) {
            return doSomeStuff();
        }
        private Object doSomeStuff() {
            //do something to get result
            return new MyObject();
        } 
    }
}

花几分钟时间,尽可能找出任何容易出现问题的路径。

不用担心,如果找不到就多花一点时间。

现在呢?

AsyncTask将在ActivityOnCreate()函数中创建并执行。但是这里会有一个问题,内部类在他的整个生命周期中都需要访问外部类。

Activity被销毁的时候会发生什么事情?AsyncTask持有一个Activity的引用,从而导致Activity不能被垃圾回收器回收。这就是所谓的内存泄漏。

旁记我以前在面试候选人时,我总是问他们怎样制造一个内存泄漏,而不是问他们关于内存泄漏的理论知识。通常都很有意思。

这里的内存泄漏实际上不仅仅在Activity自身销毁时发生,同样,由于系统配置发生变化或者系统需要更多的内存等而被强制销毁时也会发生。如果这个AsyncTask比较复杂(比如持有Activity里的Views的引用等),这还会引发程序崩溃,因为view的引用是null。

那么怎样才能防止这个问题再次发生? 让我们解释另一种类型的引用:

弱引用:弱引用是指不够强大到让系统保存在内存中的引用。如果我们尝试确定一个对象是否被强引用,而刚好是通过WeakRerences方式引用,那么这该对象将被回收。为了理解,最好是消化掉理论知识,我们通过一个实际的样例来展示怎样通过使用WeakReference来避免上一个例子中的内存泄漏:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new MyAsyncTask(this).execute();
    }
    private static class MyAsyncTask extends AsyncTask {
        private WeakReference<MainActivity> mainActivity;    
        
        public MyAsyncTask(MainActivity mainActivity) {   
            this.mainActivity = new WeakReference<>(mainActivity);            
        }
        @Override
        protected Object doInBackground(Object[] params) {
            return doSomeStuff();
        }
        private Object doSomeStuff() {
            //do something to get result
            return new Object();
        }
        @Override
        protected void onPostExecute(Object object) {
            super.onPostExecute(object);
            if (mainActivity.get() != null){
                //adapt contents
            }
        }
    }
}

注意主要的变化:内部类是通过下面的方式引用Activity的:

private WeakReference<MainActivity> mainActivity;

当Activity需要被销毁时会发生什么事情?由于它是通过弱引用的方式持有,它可以被回收。所以不会有内存泄漏发生。

旁记现在希望你已经更好的理解了什么是弱引用,你会发现一个非常有用的类 WeakHashMap。 它就是一个HashMap,除了它的keys(注意是key, 不是values)是通过弱引用的方式被引用的。这使得它对于实现高速缓存之类的实体是非常有用的

我们已经提到了更多的引用。 让我们看看在什么情况下它们是有用的,以及我们如何能够从中受益:

软引用:把软引用看做一个较强的弱引用软引用会要求垃圾回收将其保留在内存中,如果没有其他选项时才将其回收,而弱引用是被立即回收。垃圾回收算法真的是令人兴奋的东西,所以有时你会花数小时去研究而不知疲倦。但是基本规则就是:”我总是要回收弱引用。如果一个对象是软引用,我会根据系统环境来决定做什么“。这使得软引用对于实现缓存非常有用:只要内存是充裕的,我们不用担心手动移除对象。如果你想查看一个实际的例子,你可以查看这个通过软引用实现的缓存样例

虚引用:啊哈,虚引用!我想我一只手就能数过来在生产环境中我所见到的虚引用被使用的情形。一个对象如果仅仅被通过虚引用的方式引用,那么它会被垃圾回收器随心所欲的回收。没有更多的解释,没有“召回”。这使得它难以描述。为什么我们会喜欢使用这样一个东西?难道其他的还不够麻烦?为什么我选择成为一名程序员?虚引用可以被用来精准的检测一个对象是否被从内存中删除。我记得我的整个职业生涯中一共使用了两次虚引用。所以如果你现在感觉虚引用很难理解,请不要沮丧。

希望本文能够理清一点点你以前对引用的认识。在任何学习过程中,你也许想开始实践实践,把弄一下自己的代码并看看你如何改进它。首先你需要检查你的代码中是否存在内存泄漏的问题,进而使用你从这些课程中学习到的知识去消除这些令人难受的内存泄漏。如果喜欢本文或者你觉得本文对你有所帮助,请随时分享并/或者留下评论。这是对业余写作者最大的鼓励。

原文链接:Finally understanding how references work in Android and Java

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

推荐阅读更多精彩内容