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

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

Проблема заключается в следующем: мы в настоящее время пытаемся сократить classы singleton в приложении в пользу одного корневого фабричного singleton, единственной целью которого является управление другими classами менеджера. Менеджер верхнего уровня, если хотите. Это упрощает замену внедрений в тестах без выбора полного решения 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 – даже при вызове в том же streamе! Похоже, что это не проблема видимости; вместо этого статическая опорная ссылка одиночного элемента на уровне classа кажется фактически очищена сборщиком мусора. Может быть, я перехожу к выводам здесь, но может быть, это связано с тем, что сборщик мусора Android или механизм загрузки classов могут фактически выгружать classы, когда память становится недостаточной, и в этом случае единственная ссылка на экземпляр singleton исчезнет? Я не очень глубоко погружаюсь в модель памяти Java, но я полагаю, что этого не должно произойти, иначе этот общий способ реализации синглетов не будет работать на любом JVM праве?

Любая идея, почему это происходит точно?

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

ОБНОВИТЬ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Interesting Posts

C #: печать всех свойств объекта

Как купить Windows 8?

Экран (не блокировка экрана) Тайм-аут застрял на 1 минуту

Является ли первичный ключ автоматически индексированным в MySQL?

Фоновая работа в bash отключается, когда окно запуска закрыто: как этого избежать?

Получить bitmap, прикрепленное к ImageView

Аутентификация на основе токенов в ядре ASP.NET (обновлена)

Быстрые дженерики, не сохраняющие тип

Как заставить Internet Explorer (IE) ДЕЙСТВИТЕЛЬНО перезагрузить страницу?

Что такое hash-алгоритм по умолчанию, который использует членство ASP.NET?

Есть ли инструмент архива (который работает в Windows), который я могу установить «атрибут выполнения» в архиве или в zip-файле?

Использование Chrome, как найти, какие события связаны с элементом

Как перебирать файлы в каталоге с помощью Bash?

Самый простой способ сделать сценарий lua ждать / приостановить / спящий / блок на несколько секунд?

Windows 7 Extreme Slowdown

Давайте будем гением компьютера.