Статические ссылки очищаются – действительно ли Android разгружает классы во время выполнения, если они не используются?

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

Проблема заключается в следующем: мы в настоящее время пытаемся сократить классы singleton в приложении в пользу одного корневого фабричного singleton, единственной целью которого является управление другими классами менеджера. Менеджер верхнего уровня, если хотите. Это упрощает замену внедрений в тестах без выбора полного решения 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: можно обойти это, сохранив вместо этого «глобальные» ссылки на экземпляр одного приложения. Это оказалось надежным, когда нужно держать объект на протяжении всего жизненного цикла приложения.

ОБНОВИТЬ

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

Solutions Collecting From Web of "Статические ссылки очищаются – действительно ли Android разгружает классы во время выполнения, если они не используются?"

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

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

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

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

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

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

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

Вот что я думаю, что происходит:

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

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

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

Я видел подобное странное поведение с моим собственным кодом, включающим исчезающие статические переменные (я не думаю, что эта проблема имеет какое-то отношение к ключевому слову volatile). В частности, это произошло, когда я инициализировал фреймворк регистрации (например, Crashlytics, log4j), а затем после некоторого периода активности он кажется неинициализированным. Исследование показало, что это происходит после того, как OS вызывает 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