【问题标题】:Thread objects not garbage collected after being finished线程对象在完成后不会被垃圾回收
【发布时间】:2012-07-23 07:59:57
【问题描述】:

我注意到我的应用程序正在泄漏内存。这可以在 DDMS 中看到,我 设法得到一个 OutOfMemoryError。

我找到了泄漏的来源。其中一个活动有一个在后台运行的线程。该线程在onDestroy() 中停止。它运行完毕,可以在 DDMS 中看到。

现在,如果线程启动,就会发生泄漏,Activity被销毁后不会被垃圾回收,因为它是被线程引用的。 如果线程根本没有启动,一切正常。

这里有一个简单的例子来证明这一点:

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    volatile boolean finished = false;
    byte[] memoryEater = new byte[4 * 1024 * 1024];

    Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
            while (!finished) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            Log.d(getClass().getName(), "Thread finished");
        }
    });

    @Override
    protected void onDestroy() {
        super.onDestroy();
        finished = true;
    }

    public void startActivity(View view) {
        startActivity(new Intent(this, MainActivity.class));
    }

    public void startThread(View view) {
        thread.start();
    }
}

添加一个用于启动新活动的按钮和一个用于启动线程的按钮。开始新的活动。回去之后,只有在线程没有启动的情况下才会清理内存。

这种行为的原因是什么?

【问题讨论】:

  • 您能提出一个问题吗?我怀疑它的行为符合预期。每次发送 Intent 时,都会创建新的 Activity,而您似乎并没有关闭 Activity。
  • 您可以通过按返回来关闭活动。这就是泄漏发生的时候
  • 您在startActivity 中分发了对this 的引用。这个引用可以存储在某个地方吗?

标签: android debugging memory-leaks garbage-collection ddms


【解决方案1】:

线程使用的匿名可运行类将引用活动('this')。由于线程被Activity引用,而线程中的runnable引用了Activity,GC永远不会收集它们中的任何一个。

尝试做一些类似这样的事情:

private static RunnableClass implements Runnable
{
    @Override
    public void run() {
        while (!finished) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        Log.d(getClass().getName(), "Thread finished");
    }
});

Thread thread = new Thread(new RunnableClass());

【讨论】:

  • 如果活动和线程都不能从 root 到达,那么即使存在强循环依赖,两者都会在某个时候被 gc'd。 (至少在 jvms 中,如果这对于 android/dalvik 是错误的,请纠正我)
  • 活动引用线程,反之亦然,但这不应该是 gc 的问题。它们都不应从任何根目录访问。线程未运行且不是根
  • 你是对的,如果线程正在运行,那么它将是一个 root 并且不会收集任何内容。如果线程没有运行,那么如果它被保留,则必须有对其他地方的活动的引用。
【解决方案2】:

当按下“返回”或发送任何其他意图以将其从堆栈顶部删除时,活动不会被破坏。 我认为在您的 Android 操作系统内存不足之前不会调用您覆盖的 onDestroy() 方法。 从documentation可以提取:

如以下关于活动生命周期的部分所述, Android 系统为您管理活动的生命周期,因此您可以这样做 不需要完成自己的活动。调用这些方法可以 对预期的用户体验产生不利影响,只能使用 当您绝对不希望用户返回此实例时 活动。

通常,每个 Activity 实例在初始创建后都在内存中,并经过onStart()...onStop() 循环而不会被销毁。实现 onStop() 并在其中为 MainActivity 调用 finish() 并释放线程并因此进行垃圾收集。

更新:上述说法不正确。根据问题中提供的代码,没有理由不应该对 Activity 进行 GC-ed。

【讨论】:

  • 这不是真的。 OnDestroy 总是在 Activity 完成时被调用。线程结束,你可以在 logcat 和 ddms 中看到这个
  • onStop() 中调用finish() 是一个非常糟糕的主意。除其他外,当用户单击主页按钮时,它将完成您的应用程序。此外,正如您发布的 doc sn-p 中所说,打电话给finish() 通常不是一个好主意
  • @Tomasz,我同意你的看法。我应该准备好我的 Android 设置并在回答之前检查一下。然而,我认为保留这篇文章是有道理的,即使它是错误的——很多人在弄清楚 View 生命周期时都会爱上它。我会更新它。
【解决方案3】:

我一直在调查,我的发现确实令人惊讶。似乎没有真正的内存泄漏。仅当应用在 DDMS 中处于调试模式时才会发生。

DDMS 似乎以某种方式持有对那些已完成的胎面的引用,从而防止它们被 GC-ed。当我断开电话并重新连接时,我可以看到所有“泄漏”的资源都已释放。

它看起来像 DDMS 中的一个错误。

【讨论】:

    【解决方案4】:

    我刚刚发现了同样的问题。

    Tomasz,你走在正确的轨道上。 DDMS 中没有错误,您的程序中也没有内存泄漏。

    真正的问题是您在调试模式下运行程序(在 Eclipse 下)。不知何故,当 Android 在调试模式下运行时,即使退出 run() 方法,线程也不会被垃圾收集。我想可能是 Android 需要保留 Thread 才能使某些调试功能起作用。

    但是如果你在 RUN 模式下运行你的应用程序(仍然在 Eclipse 下),线程垃圾收集就会发生。 Thread 将被完全释放,您的 Activity 将被完全释放。

    【讨论】:

    • 嗯,这似乎很有可能。它可能会保留它们以用于线程状态之类的事情。所以这不是一个错误,而是一个不太完善的功能:) 毕竟它可以释放线程的可运行性。谢谢 Nellute!
    • 我用 Eclipse 中的内存分析器工具检查过,这是真的!在调试模式下,线程对象被保留!让我思考了很多!无论如何,谢谢你的回答!
    • 这里是官方文档简介(页面底部):developer.android.com/tools/debugging/index.html 它写道:“调试器和垃圾收集器目前松散集成。VM 保证调试器知道的任何对象都不是垃圾收集直到调试器断开连接。这可能导致在调试器连接时随着时间的推移对象的堆积。例如,如果调试器看到正在运行的线程,则即使在线程终止后关联的 Thread 对象也不会被垃圾收集。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-08
    相关资源
    最近更新 更多