Статические ссылки очищаются - Android выгружает классы во время выполнения, если не используется? - PullRequest
28 голосов
/ 24 февраля 2011

У меня есть вопрос о том, как работает загрузка классов / сборка мусора в Android. Мы несколько раз сталкивались с этой проблемой, и, насколько я могу судить, Android ведет себя здесь не так, как обычная JVM.

Проблема заключается в следующем: в настоящее время мы пытаемся сократить одноэлементные классы в приложении в пользу единственного корневого фабричного синглтона, единственной целью которого является управление другими классами менеджера. Менеджер высшего уровня, если хотите. Это облегчает нам замену реализаций в тестах без выбора полного решения DI, поскольку все действия и службы имеют одинаковые ссылки на эту корневую фабрику.

Вот как это выглядит:

public class RootFactory {

    private static volatile RootFactory instance;

    @SuppressWarnings("unused")
    private Context context; // I'd like to keep this for now

    private volatile LanguageSupport languageSupport;
    private volatile Preferences preferences;
    private volatile LoginManager loginManager;
    private volatile TaskManager taskManager;
    private volatile PositionProvider positionManager;
    private volatile SimpleDataStorage simpleDataStorage;

    public static RootFactory initialize(Context context) {
        instance = new RootFactory(context);
        return instance;
    }

    private RootFactory(Context context) {
        this.context = context;
    }

    public static RootFactory getInstance() {
        return instance;
    }

    public LanguageSupport getLanguageSupport() {
        return languageSupport;
    }

    public void setLanguageSupport(LanguageSupport languageSupport) {
        this.languageSupport = languageSupport;
    }

    // ...
}

initialize вызывается один раз, в Application.onCreate, т. Е. до запускается любое действие или служба. Теперь возникает проблема: метод getInstance иногда возвращается как null - даже если он вызывается в том же потоке! Похоже, это не проблема видимости; вместо этого сборщик статических одноэлементных ссылок на уровне класса, кажется, на самом деле был очищен сборщиком мусора. Возможно, я делаю поспешные выводы, но это может быть потому, что сборщик мусора Android или механизм загрузки классов могут на самом деле выгружать классы, когда памяти становится мало, и в этом случае исчезнет единственная ссылка на экземпляр singleton ? Я не очень глубоко разбираюсь в модели памяти Java, но, полагаю, этого не должно произойти, иначе этот обычный способ реализации синглетонов не сработает на любой JVM, верно?

Есть идеи, почему именно это происходит?

PS: можно обойти это, сохранив вместо этого «глобальные» ссылки на один экземпляр приложения. Это оказалось надежным, когда нужно держать объект в течение всего срока службы приложения.

UPDATE

Очевидно, мое использование летучих здесь вызвало некоторую путаницу. Мое намерение состояло в том, чтобы гарантировать, что текущее состояние статической ссылки всегда видимо для всех потоков, обращающихся к ней. Я должен сделать это, потому что я пишу и читаю эту ссылку из более чем одного потока: в обычном приложении, запускаемом только в основном потоке приложения, но в тестовом прогоне инструментария, где объекты заменяются на mocks, я пишу его из Поток инструментовки и читать его в потоке пользовательского интерфейса. Я мог бы также синхронизировать вызов с getInstance, но это дороже, так как требует блокировки объекта. См. Каков эффективный способ реализации одноэлементного шаблона в Java? для более подробного обсуждения этого вопроса.

Ответы [ 4 ]

4 голосов
/ 24 февраля 2011

Я никогда в своей жизни не видел статического члена данных, объявленного volatile. Я даже не уверен, что это значит.

Элементы статических данных будут существовать до тех пор, пока процесс не будет завершен или пока вы не избавитесь от них (например, null из статической ссылки). Процесс может быть прекращен после того, как все действия и услуги будут предварительно закрыты пользователем (например, кнопкой НАЗАД) и вашим кодом (например, stopService()). Процесс может быть прерван даже с активными компонентами, если Android отчаянно не хватает оперативной памяти, но это довольно необычно. Процесс может быть прерван с помощью действующей службы, если Android считает, что ваша служба находится в фоновом режиме слишком долго, хотя она может перезапустить эту службу в зависимости от значения, возвращаемого вами из onStartCommand().

Классы не выгружены, точка, за исключением окончания процесса.

Чтобы обратиться к другим точкам @ sergui, действия могут быть уничтожены с сохранением состояния экземпляра (хотя и в ОЗУ, а не в «фиксированном хранилище») для освобождения ОЗУ. Android будет стремиться сделать это до завершения активных процессов, хотя, если он уничтожает последнее действие для процесса и нет запущенных служб, этот процесс будет основным кандидатом на завершение.

Единственное, что значительно странно в вашей реализации, это использование volatile.

4 голосов
/ 28 июня 2012

И вы (@Matthias), и Марк Мерфи (@CommonsWare) правы в том, что вы говорите, но суть кажется утраченной. (Использование volatile является правильным и классы не выгружаются.)

Суть вопроса в том, откуда вызывается initialize.

Вот что я думаю:

  • Вы звоните инициализировать с Activity
  • Android требуется больше памяти, убивает целое Process
  • Android перезагружает Application и верх Activity
  • Вы звоните getInstance, который вернет null, так как initialize не был вызван

Поправь меня, если я ошибаюсь.

2 голосов
/ 24 февраля 2011

Статические ссылки очищаются всякий раз, когда система чувствует, что это так, и ваше приложение не является верхним уровнем (пользователь не запускает его явно). Всякий раз, когда ваше приложение свернуто и ОС требует больше памяти, оно либо убивает ваше приложение, либо сериализует его в фиксированном хранилище для последующего использования, но в обоих случаях статические переменные стираются. Кроме того, всякий раз, когда ваше приложение получает ошибку Force Close, вся статика также удаляется. По своему опыту я понял, что всегда лучше использовать переменные в объекте Application, чем статические переменные.

1 голос
/ 06 января 2016

Я видел похожее странное поведение с моим собственным кодом, включающим исчезновение статических переменных (я не думаю, что эта проблема имеет какое-либо отношение к ключевому слову volatile). В частности, это произошло, когда я инициализировал каркас ведения журналов (например, Crashlytics, log4j), а затем, после некоторого периода активности, он кажется неинициализированным. Расследование показало, что это происходит после того, как ОС вызывает onSaveInstanceState(Bundle b).

Ваши статические переменные хранятся в Classloader, который содержится в процессе вашего приложения. По данным Google:

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

http://developer.android.com/guide/topics/processes/process-lifecycle.html

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

Один из обходных путей, который я использовал для инициализации своей инфраструктуры ведения журналов, - это для всех моих Деятельностей расширить базовый класс, где я переопределяю onCreate, и проверять инициализацию и, при необходимости, повторно инициализировать.

Я думаю, что официальным решением является использование обратного вызова onSaveInstanceState(Bundle b) для сохранения всего, что понадобится вашей Деятельности позже, а затем повторная инициализация в onCreate(Bundle b) при b != null.

Google объясняет это лучше всего:

http://developer.android.com/training/basics/activity-lifecycle/recreating.html

...