澳门新葡萄京官网注册终极理解弱引用在Android和Java中的工作原理[译]

本文讲的是彻底理解引用在 Android 和 Java 中的工作原理,

  • 原文地址:Finally understanding how references work in Android
    and
    Java

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

几周前,我很荣幸地参加了在波兰举行的 Mobiconf
,移动开发者参加的最好的研讨会之一。我的朋友兼同事 Jorge Barroso
做了个名为“最好的做法”的演说 ,这让我在听后很有感触:

  • 原文作者:Enrique López
    Mañas
  • 译文出自:掘金翻译计划
  • 译者:jacksonke
  • 校对者:jamweak,PhxNirvana

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

对于一个 Android 开发者,如果你不使用 WeakReferences,这是有问题的。

几周前,我很荣幸地参加了在波兰举行的Mobiconf,移动开发者参加的最好的研讨会之一。我的朋友兼同事
Jorge Barroso
做了个名为“最好(良好)的做法”的演说 ,这让我在听后很有感触:

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

举个恰当的例子,几个月前,我发布了我的最后一本书 “Android High
Performance”, 联席作者是 Diego Grancini。最热门的章节之一就是讨论
Android
的内存管理。在本章中,我们介绍了移动设备中内存的工作原理,内存泄漏是如何发生的,为什么这个是重要的,以及我们可以应用哪些技术来避开它们。因为我从开发
Android
起,就常常看到这么种倾向:轻视甚至无视一切与内存泄漏和内存管理相关的问题。已经满足开发需求了,为何要庸人自扰呢?我们总是急于开发新的功能,我们宁愿在下一个
Sprint
演示中呈现一些可见的东西,也不会关心那些没有人一眼就能看到的东西。

对于一个 Android 开发者,如果你不使用 WeakReferences,这是有问题的。

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

这无疑是导致技术债务一个活生生的例子。
我甚至可以补充地说,技术债务在现实世界中也有一些影响,那是我们不能用单元测试衡量的:失望,开发者间的摩擦,低质量的软件和积极性的丧失。这种影响难以衡量的原因是在于它们常常发生在长远的将来的某个时间点。这有点像政客:如果我只当政
8 年,为何我要烦心 12
年后将要发生的事呢?除了在软件开发,一切都以更快的方式。

举个恰当的例子,几个月前,我发布了我的最后一本书 “Android High
Performance”,
联席作者是 Diego
Grancini。最热门的章节之一就是讨论
Android
的内存管理。在本章中,我们介绍了移动设备中内存的工作原理,内存泄漏是如何发生的,为什么这个是重要的,以及我们可以应用哪些技术来避开它们。因为我从开发
Android
起,就常常看到这么种倾向:轻视甚至无视一切与内存泄漏和内存管理相关的问题。已经满足开发需求了,为何要庸人自扰呢?我们总是急于开发新的功能,我们宁愿在下一个斯普林特演示中呈现一些可见的东西,也不会关心那些没有人一眼就能看到的东西。

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

编写软件开发中应该采纳的设计思想可能需要一些大篇文章,而且已经有很多书和文章可供您参考。然而,简要地解释不同类型的内存引用,它们具体是什么,以及如何在
Android 中使用,这是个相对简短的任务,这也是我想在本文中做的。

这无疑是导致技术债务一个活生生的例子。
我甚至可以补充地说,技术债务在现实世界中也有一些影响,那是我们不能用单元测试衡量的:失望,开发者间的摩擦,低质量的软件和积极性的丧失。这种影响难以衡量的原因是在于它们常常发生在长远的将来的某个时间点。这有点像政客:如果我只当政
8 年,为何我要烦心 12
年后将要发生的事呢?除了在软件开发,一切都以更快的方式。

首先:Java中什么是引用?

首先:Java 中的引用是什么?

编写软件开发中应该采纳的设计思想可能需要一些大篇文章,而且已经有很多书和文章可供您参考。然而,简要地解释不同类型的内存引用,它们具体是什么,以及如何在
Android 中使用,这是个相对简短的任务,这也是我想在本文中做的。

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

引用指向了一个对象,你能通过引用访问对象。

首先:Java 中的引用是什么?

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

Java 默认有 4 种类型的引用:强引用、软引用、弱引用
和虚引用。部分人认为只有强引用和弱引用两种类型的引用,而弱引用有两个层次的弱化。我们习惯于将生活中的一切事物归类,那种毅力堪比植物学家对植物的分类的。不论你觉得哪种分类更好,首先你需要去理解这些引用。然后你可以找出自己的分类。

引用指向了一个对象,你能通过引用访问对象。

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

各种引用都是什么意思?

Java 默认有 4
种类型的引用:强引用(StrongReference)软引用(SoftReference)弱引用(WeakReference)

虚引用(PhantomReference)。部分人认为只有强引用和弱引用两种类型的引用,而弱引用有两个层次的弱化。我们习惯于将生活中的一切事物归类,那种毅力堪比植物学家对植物的分类的。不论你觉得哪种分类更好,首先你需要去理解这些引用。然后你可以找出自己的分类。

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

StrongReference: 强引用是 Java
中最为常见的引用类型。任何时候,当我们创建了一个对象,强引用也同时被创建了。比如,当我们这么做:

各种引用都是什么意思?

MyObject object = new MyObject();

MyObject object = new MyObject();

StrongReference: 强引用是 Java
中最为常见的引用类型。任何时候,当我们创建了一个对象,强引用也同时被创建了。比如,当我们这么做:

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

一个新的 MyObject 对象被创建,指向它的强引用保存在 object
中。你还在看吧? 嗯,更有意思的事情来了,这个 object
是可以强行到达的——意思就是,它可以通过一系列强引用找到,这将会阻止垃圾回收机制回收它,然而,这正是是我们最想要的。现在,我们来看个例子。

MyObject object = new MyObject();
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();
        } 
    }
}

花几分钟,尝试去找可能出现问题的点。

一个新的 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 对象会在 Activity onCreate()
方法中创建并运行。但这里有个问题:内部类在它的整个生命周期中是会访问外部类。

不用担心,如果一时找不到,那再花点时间看看。

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

如果 Activity 被 destroy 掉时,会发生什么? AsyncTask 仍然持有 Activity
的引用,所以 Activity 是不能被 GC 回收的。这就是我们所说的内存泄漏。

现在呢?

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

旁注
:以前,我曾经对合适的人进行访谈,我问他们如何创建内存泄漏,而不是询问内存泄漏的理论方面。这总是更有趣!

AsyncTask 对象会在 Activity onCreate()
方法中创建并运行。但这里有个问题:内部类在它的整个生命周期中是会访问外部类。

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

内存泄漏实际上不仅发生在 Activity
自身销毁的时候,配置的改变或系统需要更多的内存时,也可能系统强行销毁。如果
AsyncTask 复杂点,它甚至会导致崩溃,因为 view 的引用是 null。

如果 Activity 被 destroy 掉时,会发生什么? AsyncTask 仍然持有
Activity 的引用,所以 Activity 是不能被 GC
回收的。这就是我们所说的内存泄漏。

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

那么,要如何防止这种问题再次发生呢?我们接下来介绍另一种类型的引用:

旁注
:以前,我曾经对合适的人进行访谈,我问他们如何创建内存泄漏,而不是询问内存泄漏的理论方面。这总是更有趣!

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

WeakReference:弱引用是引用强度不足以将对象保持在内存中的引用。如果垃圾回收机制试图确定对象的引用强度,如果恰好是通过
WeakReferences
引用,那么该对象将被垃圾回收。为了便于理解,最好是先抛开理论,用上个例子来说明如何使用
WeakReference 来避免内存泄漏:

内存泄漏实际上不仅发生在 Activity
自身销毁的时候,同时还发生在由于配置的改变或需要更多的内存而被系统强行销毁等时刻。如果
AsyncTask 复杂点(比如,持有 Activity 上的 View
的引用),它甚至会导致崩溃,因为 view 的引用是 null。

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

现在注意一个主要区别:Activity 是这样被内部类引用的:

那么,要如何防止这种问题再次发生呢?我们接下来介绍另一种类型的引用:

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
            }
        }
    }
}

private WeakReference mainActivity;

WeakReference:弱引用是引用强度不足以将对象保持在内存中的引用。如果垃圾回收机制试图确定对象的引用强度,如果恰好是通过
WeakReferences
引用,那么该对象将被垃圾回收。为了便于理解,最好是先抛开理论,用上个例子来说明如何使用
WeakReference 来避免内存泄漏:

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

这样做有什么差别呢? 当 Activity 不存在时,由于它是被 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;    

        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
            }
        }
    }
    }
private WeakReference<MainActivity> mainActivity;

旁注: 如果你现在对 WeakReferences 有预期的更好的了解,你会发现类
WeakHashMap 是很有用的。 它完全就是一个 HashMap,除了使用 WeakReferences
引用键。 这使得它们对于实现诸如缓存之类的实体非常有用。

现在注意一个主要区别:Activity 是这样被内部类引用的:

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

我们提到过更多的引用类型。
让我们看看它们在什么地方有用,以及我们如何能从中受益:

private WeakReference mainActivity;

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

SoftReference:
软引用可以作为一个引用强度更强的弱引用。在弱引用将被立即回收的情形下,软引用会向
GC
请求留在内存中,除非没有其他选项。垃圾回收算法真的很有意思,你可以几个小时内沉醉于研究它,而不会感到疲惫。但大体上,垃圾回收会这么解说“我会永远收回弱引用。
如果对象是 软引用,我将基于具体条件决定是否回收。”
这使得软引用对于实现缓存非常有用:只要内存足够,我们就不必担心手动删除对象。
如果你想看实际中的例子,你可以查看这个例子,用软引用实现的缓存。

这样做有什么差别呢? 当 Activity 不存在时,由于它是被 WeakReference
持有的,可以被收集。 因此,不会发生内存泄漏。

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

PhantomReference:额,虚引用!
在实际产品开发中,我见过的,使用的次数不会超过 5 次。
垃圾收集器能随时回收虚引用
持有的对象,只要它乐意。没有进一步的解释,没有回调,这使得它难以描述。为什么我们要使用这样的东西?
其他几个的问题还不够吗? 为什么我会选择成为程序员?
虚引用可以精确地用于检测对象是否已从内存中删除。
说实话,在我的整个职业生涯中不得不用虚引用的场景只有两次。
所以,即便你现在不是很难理解,也不要感到有压力。

旁注: 如果你现在对 WeakReferences 有预期的更好的了解,你会发现类
WeakHashMap 是很有用的。 它完全就是一个 HashMap,除了使用
WeakReferences 引用键(键,而不是值)。
这使得它们对于实现诸如缓存之类的实体非常有用。

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

希望这有稍微消除点之前你对引用的疑虑。作为学习的东西,或许你现在想要来点练习,玩你自己的代码,看看能怎么改进它。第一步应该是看看有没有内存泄漏,然后看看能否通过这里所学的知识去改掉那些令人讨厌的内存泄漏。如果你喜欢这篇文章,或者它确实帮到了你,请随意分享或者留下你的评论。这也是我这位业余写手的动力。

我们提到过更多的引用类型。
让我们看看它们在什么地方有用,以及我们如何能从中受益:

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

SoftReference:
软引用可以作为一个引用强度更强的弱引用。在弱引用将被立即回收的情形下,软引用会向
GC
请求留在内存中,除非没有其他选项(否则是不会回收软引用持有的对象)。垃圾回收算法真的很有意思,你可以几个小时内沉醉于研究它,而不会感到疲惫。但大体上,垃圾回收会这么解说“我会永远收回弱引用。
如果对象是 软引用,我将基于具体条件决定是否回收。”
这使得软引用对于实现缓存非常有用:只要内存足够,我们就不必担心手动删除对象。
如果你想看实际中的例子,你可以查看这个例子,用软引用实现的缓存。

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

PhantomReference:额,虚引用!
在实际产品开发中,我见过的,使用的次数不会超过 5 次。
垃圾收集器能随时回收虚引用
持有的对象,只要它乐意。没有进一步的解释,没有回调,这使得它难以描述。为什么我们要使用这样的东西?
其他几个的问题还不够吗? 为什么我会选择成为程序员?
虚引用可以精确地用于检测对象是否已从内存中删除。
说实话,在我的整个职业生涯中不得不用虚引用的场景只有两次。
所以,即便你现在不是很难理解,也不要感到有压力。

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

希望这有稍微消除点之前你对引用的疑虑。作为学习的东西,或许你现在想要来点练习,玩你自己的代码,看看能怎么改进它。第一步应该是看看有没有内存泄漏,然后看看能否通过这里所学的知识去改掉那些令人讨厌的内存泄漏。如果你喜欢这篇文章,或者它确实帮到了你,请随意分享或者留下你的评论。这也是我这位业余写手的动力。

感谢我的同事
Sebastian
对这篇文章的投入!

发表评论

电子邮件地址不会被公开。 必填项已用*标注