Объекты темы, а не мусор, собранный после завершения

Я заметил, что в моем приложении происходит утечка памяти. Это можно увидеть в 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();
    }
}

Добавьте одну кнопку для запуска новой активности и одну для запуска потока. Начать новую деятельность. После возврата память будет очищена, только если поток не запущен.

В чем причина такого поведения?

Ответ 1

Я только что понял эту проблему.

Томаш, ты на правильном пути. В DDMS нет ошибки, и в вашей программе нет утечки памяти.

На самом деле проблема заключается в том, что вы запускаете свою программу в режиме DEBUG (под Eclipse). Так или иначе, когда Android работает в режиме DEBUG, потоки не собираются с мусором даже после выхода метода run(). Я думаю, что, вероятно, Android должен поддерживать Thread для некоторых функций отладки.

Но если вы запустили приложение в режиме RUN (все еще под Eclipse), происходит сборка мусора Thread. Тема будет полностью освобождена, и ваша активность будет полностью освобождена.

Ответ 2

Я продолжал расследование, и то, что я нашел, действительно удивляет. Кажется, нет реальной утечки памяти. Это происходит только тогда, когда приложение находится в режиме отладки в DDMS.

DDMS, похоже, каким-то образом держит ссылки на те готовые ступени, не позволяя им быть GC-ed. Когда я отсоединяю телефон и подключаюсь снова, я вижу, что все "утеченные" ресурсы были освобождены.

Он выглядит как ошибка в DDMS.

Ответ 3

Анонимный класс runnable, используемый потоком, будет ссылаться на активность ('this'). Поскольку поток ссылается на активность, а runnable в потоке ссылается на активность, 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());

Ответ 4

Действия не уничтожаются при нажатии "назад" или каких-либо других намерений, чтобы удалить его из верхней части стека. Я не думаю, что ваш метод overderen onDestroy() когда-либо называется, пока ваша ОС Android не исчерпается. Из документации можно извлечь:

Как обсуждалось в следующем разделе о жизненном цикле активности, система Android управляет жизнью для вас, поэтому вы делаете не нужно заканчивать свою деятельность. Вызов этих методов мог отрицательно сказываются на ожидаемом опыте пользователя и должны использоваться только когда вы абсолютно не хотите, чтобы пользователь возвращался к этому экземпляру деятельности.

Обычно каждый экземпляр Activity кладется в памяти после первоначального создания и проходит цикл onStart()...onStop() без разрушения. Внесите onStop() и вызовите finish() в нем для MainActivity с выпуском Thread и, следовательно, собранным мусором.

UPDATE. Вышеприведенное утверждение неверно. Основываясь на коде, предоставленном в вопросе, нет причин, по которым деятельность не должна быть GC-ed.