如何/何时收集处理程序垃圾?

Posted

技术标签:

【中文标题】如何/何时收集处理程序垃圾?【英文标题】:How/when is a Handler garbage collected? 【发布时间】:2011-07-03 05:20:20 【问题描述】:

在我的一个类中,我有以下代码:

mHandler = createHandler();

private Handler createHandler() 
    return new Handler() 
        public void handleMessage (Message msg) 
            update();
            if (!paused) 
                sendEmptyMessageDelayed(0, 300);
            
        
    ;

文档说:

http://developer.android.com/reference/android/os/Handler.html

每个 Handler 实例都与单个线程和该线程的消息队列相关联

所以如果我理解正确,只要应用程序线程正在运行,处理程序就不会被垃圾收集,对吗?

在我的具体示例中,由于 Handler 是一个匿名内部类,它具有对封闭对象的隐式引用以及它所指向的对象的整个层次结构。这在我看来就像是内存泄漏的秘诀。

顺便说一句,我可以让处理程序停止发送消息(这就是为什么我有 if (!paused))但这不会让它被 GCed,对吧?

那么有没有办法从消息队列中移除 Handler 并让它被 GCed 呢?

【问题讨论】:

【参考方案1】:

对 Handler 源代码的检查揭示了更多细节。

以下是 Romain Guy 添加的 Handler() 构造函数中的一些调试代码:

if (FIND_POTENTIAL_LEAKS) 
  final Class<? extends Handler> klass = getClass();
  if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
      (klass.getModifiers() & Modifier.STATIC) == 0) 
    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
      klass.getCanonicalName());
  

警告很明确:不要将 Handler 子类声明为内部类。

Handler的looper是从一个静态ThreadLocal实例中获取的:

mLooper = Looper.myLooper();

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static final Looper myLooper() 
    return (Looper)sThreadLocal.get();

泄漏解剖:

主应用线程保留 Looper 及其 MessageQueue,队列中的 Messages 保留指向其目标 Handler 的链接,并且 Handler——除非它是一个对你的 Activity 有 WeakReference 的静态嵌套类——将保留你的Activity 及其视图。

您可以尝试通过清理您的消息来堵住这个漏洞:

handler.removeMessages(what);

但这说起来容易做起来难。

另见On Memory Leaks in Java and in Android

【讨论】:

所以如果所有消息都应该立即执行,就没有真正的内存泄漏危险吗? 对,如果您有延迟消息,那么它将泄漏。见:***.com/a/11408340/550471【参考方案2】:

在我的具体示例中,由于 Handler 是一个匿名内部类,它具有对封闭对象的隐式引用以及它所指向的对象的整个层次结构。

您可以通过使用static 嵌套类而不是匿名内部类将潜在泄漏的影响降低到几乎没有。

【讨论】:

谢谢,这是最好的答案,实际上解决了我的问题。但我仍然认为它是 Android 的设计弱点,如果一旦创建 Handler 就无法删除它。【参考方案3】:

不,停止发送消息不会使 GC 工作。正如文档指出的那样,它绑定到创建它的线程。如果线程正在运行,则处理程序不会被 GC 回收。

为什么您认为这会导致内存泄漏? “隐式引用封闭对象”是什么意思?

【讨论】:

@Sarstine - 任何非静态内部类都有对其封闭类实例的隐式引用,因此它可以使用封闭类的实例方法和属性。 没错,隐式引用确实使一些内存保持活动状态,但这不是内存泄漏。外部对象保存在内存中,因为内部对象仍然可以使用它;实际上,如果您不需要它,请将内部类声明为静态的。最后,外部类仍然可以访问。如果外部类无法访问并且仍保留在内存中,这将是内存泄漏。例如,通过一些循环引用,这就是孤立岛。垃圾收集器已经可以处理这种情况了。 就我而言,这是内存泄漏,因为我不再使用外部对象,实际上我想摆脱它。问题是 Handler 仍然有对它的引用,而 Thread 有对 Handler 的引用,没有办法摆脱后者的引用(除非我原来的问题有答案)。

以上是关于如何/何时收集处理程序垃圾?的主要内容,如果未能解决你的问题,请参考以下文章

java垃圾是怎么回收的,回收算法

对象何时有资格进行垃圾收集?

SCJP 想知道对象何时被垃圾收集的问题?

页面加载后对象是不是有资格进行垃圾收集?

确定JVM垃圾收集器的运行时

如果没有保存引用,SystemTimers.Timer 何时被视为垃圾?