【问题标题】:Reference to final object in anonymous class constructor a leak?匿名类构造函数中对最终对象的引用泄漏?
【发布时间】:2014-10-31 07:25:22
【问题描述】:

我正在将一个可运行文件传递给服务。可运行的持续时间可能比通过它的片段/活动长。我想知道以下代码 sn-p 是否会通过在可运行文件中维护引用来泄漏 frag 对象?我意识到我可以将包含 frag 的整行移到可运行文件之外(就像我对“String key = frag ...”所做的那样),但我只是想了解匿名类如何/何时泄漏对象。

我真的不确定。 runnable 只需要 frag 在初始化实例时确定一个变量,并在此处内联初始化。所以理论上,创建内联实例后不需要对 frag 有任何引用。

如果我在 run() 函数中有对 frag 的引用,我认为会保证泄漏,因为它需要让 frag 保持活动状态才能在将来的某个时间引用它(并且 frag 很可能是 gc' d当时,但仅供参考)。

private static void fg_downloadFile(final FragMyDrive frag, final File file, final Drive drive){

    final String key = frag.getCastedActivity().getKey();

    Runnable rx_downloadFile = new Runnable() {

        Context ServiceContext = frag.getCastedActivity().mService;

        @Override
        public void run() {

            bg_downloadFile(ServiceContext, key, file, drive);
        }
    }; 

    //.... Submit runnable to service...
}

【问题讨论】:

    标签: java android memory-leaks anonymous-class


    【解决方案1】:

    您可能知道,当在外部声明变量但在匿名类中使用它们时,您必须使变量final。这里的 Java 技巧是将所有这些变量复制到该匿名类的隐式生成的实例字段中。

    话虽如此,这意味着确实有实例字段(在您的可运行文件中)保存外部范围的所有访问变量的副本。在您的示例中,它还会引用FragMyDrive,因为您只是在访问它。

    所有这些对象都符合垃圾回收的条件,同时您的可运行对象也符合垃圾回收的条件。这意味着在您的可运行文件中对您的FragMyDrive 的引用会在该对象运行时保持该对象处于活动状态。

    将这些参考范围缩小到您真正需要的范围总是一个好主意:

    private static void fg_downloadFile(final FragMyDrive frag, final File file, final Drive drive){
        final String key = frag.getCastedActivity().getKey();
        final Context context = frag.getCastedActivity().mService;
    
        Runnable rx_downloadFile = new Runnable() {
            @Override
            public void run() {
                bg_downloadFile(context, key, file, drive);
            }
        }; 
        //.... Submit runnable to service...
    }
    

    这里唯一(隐式生成的)实例字段是:

    String key
    Context context
    File file
    Drive drive
    

    【讨论】:

    • 是的,这实际上符合我的代码。现在想想,我认为这种解决方法总是可用的。我想从风格上讲,我只是喜欢将所有的类变量都放在类定义中。有点强迫症,我只是好奇地想知道我是否可以把它们放在那里。我认为您是对的,答案是如果我尝试使用 frag 之类的对象来初始化显式声明的类变量,它会泄漏。解决方法并不依赖于需要知道该答案....使其安全。
    • @NameSpace "我只是喜欢将我的所有类变量都放在类定义中" => 然后让它成为一个带有显式构造函数的显式类。这很可能是private static final class ...。您正在声明一个匿名类,并且它始终绑定到方法的范围(因此可以访问所有最终变量)。这是匿名类的特性
    【解决方案2】:

    我不确定我是否正确理解了您对 inline 这个词的使用,但无论如何,我认为这里没有泄露任何内容。

    Var final String key 只是静态函数 fg_downloadFile() 中的一个本地引用,因此在函数结束时超出范围时将其释放。

    只要Runnable 线程存在,Runnable 匿名内部类可能仍然持有引用(同样,不是frag,而是其中的某些子属性)。 (更准确地说,编译器实际上生成了 Runnableimplementationfinal 成员变量,这些变量将任何隐式使用的引用复制到 fg_downloadFile() 的范围内可用的任何内容。)

    但无论如何,一旦所有代码都执行完毕,所有对frag(的子成员)的引用都将被删除,垃圾收集器可以捡起垃圾。

    希望这会有所帮助。

    【讨论】:

    • 我知道 frag 不会在“key = frag”行中泄漏,我想知道的是 serviceContext = frag 行,它在可运行文件中。我们是否都同意如果我在 runnable 的 run() 函数中引用 frag 肯定会泄漏?好吧,如果构造函数被视为任何旧函数,那么通过类比它就会泄漏......我们必须假设构造函数不是任何旧函数,并且编译器足够聪明,知道匿名函数的构造函数只需要一次这样它就可以在初始化实例后丢弃片段引用。
    • 我同意Runnable 中的引用将在fg_downloadFile() 完成后保留,但这不是泄漏。正如下面的@seelenvirtuose 所表达的,一旦Runnable 被清理,GC 最终将清理所有对象。在Runnable“实例”中包含隐式成员的全部意义在于确保被引用对象的生存时间与run() 函数中发生的任何事情保持一致。这与 C++ 中的 RAII 非常接近。
    • 我意识到您依赖于内存泄漏的定义,其中 java 中的任何内容实际上都不是泄漏。我关心的实际上是服务保持片段存活多长时间,因为任务可能无限期地位于服务中(例如等待 wifi)。在 java/android 中,leak 经常被用来指“保留一个引用,从而防止 GC 收集它”。 android-developers.blogspot.com/2009/01/…
    • 在这种情况下,问题不在于 Java 或可运行对象中的引用,而在于(池化)引用的生命周期。在任何情况下,如果您的运行函数不需要(隐式)传递给可运行对象的所有信息,只需确保它没有被传递。不要访问您不需要的内容。无论如何,我看不出final 的存在与否与它有什么关系。
    • 好的,我明白你的意思了。确实,Java 的匿名内部类中发生了一些(不受欢迎的)魔法——强大的力量伴随着巨大的责任。 Java 8 的 lambdas 更是如此。我想您应该格外小心,以确保您的 Runnable 不会(隐式)引用任何它不需要的东西(因为它的整个生命周期),以防您期望 bg_downloadFile() 持续很长时间/无限期。我会提倡使用常规类或静态内部类来为其中的内容提供显式接口。
    【解决方案3】:

    有趣的问题。你有没有像 Objective C 这样没有自动垃圾收集的编程经验?

    但是要回答您的问题,我不明白为什么会泄漏。 我想会发生的是 Runnable 将保留对 frag 的引用以供自己使用,但是在将来的某个时候,一旦服务不再需要 frag ,它也会被垃圾收集,因此它不是指它。

    随着垃圾收集的工作,一旦不再引用对象,它将被垃圾收集并释放。

    【讨论】:

    • 好吧,我意识到(几乎)gc 语言中的所有内存泄漏都不完全符合正常内存泄漏的定义。这里我担心的是服务在文件下载之前保持片段活动......任务可能会在队列中等待一段时间,例如等待启用wifi。
    • 我猜“正常内存泄漏”这个词是相对于你用来编程的环境而言的。是的,片段肯定会存活一段时间。是否还有其他一些因素导致这种不受欢迎的大量内存使用等?
    猜你喜欢
    • 1970-01-01
    • 2014-02-20
    • 1970-01-01
    • 2017-01-28
    • 2015-12-28
    • 2020-04-30
    • 2016-09-26
    • 2011-04-25
    • 2018-10-03
    相关资源
    最近更新 更多