Las referencias estáticas se borran – Android descarga clases en tiempo de ejecución si no se utiliza?

Tengo una pregunta específica sobre cómo funciona la recaudación de clases / recolección de basura en Android. Hemos tropezado con este tema unas cuantas veces, y por lo que puedo decir, Android se comporta diferente aquí de una JVM ordinaria.

El problema es el siguiente: en la actualidad estamos tratando de reducir las clases singleton en la aplicación a favor de una única raíz singleton de fábrica, cuyo único propósito es administrar otras clases de gestor. Un gerente de nivel superior si lo desea. Esto nos facilita sustituir las implementaciones en las pruebas sin optar por una solución DI completa, ya que todas las actividades y servicios comparten la misma referencia a esa fábrica raíz.

Así es como se ve:

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 se llama una vez, en Application.onCreate , es decir, antes de iniciar cualquier actividad o servicio. Ahora, aquí está el problema: el método getInstance veces vuelve como null – incluso cuando se invoca en el mismo hilo! Parece que no es un problema de visibilidad; En su lugar, la retención de referencia de singleton estática en el nivel de clase parece haber sido realmente eliminada por el recolector de basura. Tal vez estoy saltando a las conclusiones de aquí, pero ¿podría ser esto porque el recolector de basura de Android o mecanismo de carga de clase realmente puede descargar clases cuando la memoria se vuelve escasa, en cuyo caso la única referencia a la instancia singleton desaparecerá? No estoy muy profundamente en el modelo de memoria de Java, pero supongo que no debería suceder, de lo contrario esta forma común de implementar singletons no funcionaría en cualquier derecho JVM?

¿Alguna idea de por qué esto está sucediendo exactamente?

PS: uno puede trabajar alrededor de esto manteniendo referencias "globales" en la sola instancia de la aplicación en lugar de otro. Eso ha demostrado ser confiable cuando uno debe mantener el objeto a lo largo de toda la vida útil de una aplicación.

ACTUALIZAR

Al parecer mi uso de volátil aquí causó cierta confusión. Mi intención era asegurar que el estado actual de la referencia estática siempre esté visible para todos los subprocesos que acceden a él. Debo hacerlo porque estoy escribiendo y leyendo esa referencia desde más de un hilo: En una ejecución de aplicación normal, solo en el hilo principal de la aplicación, pero en una prueba de instrumentación, donde los objetos son reemplazados por mocks, lo escribo desde el Instrumentación y lo lee en el hilo de la interfaz de usuario. También podría haber sincronizado la llamada a getInstance , pero eso es más caro ya que requiere reclamar un bloqueo de objetos. Vea ¿Cuál es una manera eficiente de implementar un patrón singleton en Java? Para una discusión más detallada alrededor de esto.

Nunca he visto en mi vida un miembro de datos estáticos declarado volatile . Ni siquiera estoy seguro de lo que eso significa.

Los miembros de datos estáticos existirán hasta que se termine el proceso o hasta que se deshaga de ellos (por ejemplo, null la referencia estática). El proceso puede ser terminado una vez que todas las actividades y servicios sean cerrados proactivamente por el usuario (por ejemplo, el botón BACK) y su código (por ejemplo, stopService() ). El proceso puede ser terminado incluso con componentes en vivo si Android es desesperadamente corto en RAM, pero esto es bastante inusual. El proceso se puede terminar con un servicio en vivo si Android piensa que su servicio ha estado en segundo plano demasiado tiempo, aunque puede reiniciar ese servicio dependiendo de su valor de retorno de onStartCommand() .

Las clases no se descargan, período, antes de finalizar el proceso.

Para tratar el otro de los puntos de @ sergui, las actividades pueden ser destruidas, con el estado de la instancia almacenado (aunque en RAM, no "almacenamiento fijo"), para liberar RAM. Android tiende a hacerlo antes de finalizar los procesos activos, aunque si destruye la última actividad de un proceso y no hay servicios en ejecución, ese proceso será un candidato principal para la terminación.

La única cosa significativamente extraña sobre su aplicación es su uso de volatile .

Tanto usted (@Matthias) y Mark Murphy (@CommonsWare) son correctos en lo que usted dice, pero la esencia parece perdido. (El uso de volatile es correcto y las clases no se descargan.)

El quid de la pregunta es de donde se initialize desde.

Esto es lo que creo que está sucediendo:

  • Estás llamando inicializar desde una Activity
  • Android necesita más memoria, mata todo el Process
  • Android reinicia la Application y la Activity principal
  • Llamar getInstance que devolverá null , ya que initialize no se llamó

Corrígeme si estoy equivocado.

Las referencias estáticas se borran siempre que el sistema se sienta como él y su aplicación no es de alto nivel (el usuario no lo está ejecutando explícitamente). Cada vez que su aplicación se minimice y el sistema operativo quiere un poco más de memoria, ya sea matar su aplicación o serializar en almacenamiento fijo para su uso posterior, pero en ambos casos las variables estáticas se borran. Además, cada vez que su aplicación obtiene un error de Force Close , todas las estadísticas se borran también. En mi experiencia vi que siempre es mejor usar variables en el objeto Application que variables estáticas.

He visto similar comportamiento extraño con mi propio código que implica la desaparición de variables estáticas (no creo que este problema tiene nada que ver con la palabra clave volátil). En particular, esto ha surgido cuando he inicializado un marco de registro (por ejemplo, Crashlytics, log4j), y luego, después de algún período de actividad, parece no haber sido inicializado. La investigación ha demostrado que esto sucede después de que el sistema operativo llama onSaveInstanceState(Bundle b) .

El Classloader contiene las variables estáticas contenidas en el proceso de la aplicación. Según google:

Una característica inusual y fundamental de Android es que la vida útil de un proceso de aplicación no está controlada directamente por la propia aplicación. En su lugar, el sistema lo determina mediante una combinación de las partes de la aplicación que el sistema sabe están en ejecución, la importancia de estas cosas para el usuario y la cantidad de memoria disponible en el sistema.

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

Lo que esto significa para un desarrollador es que no puede esperar que las variables estáticas permanezcan inicializadas indefinidamente. Necesitas confiar en un mecanismo diferente para la persistencia.

Una solución que he utilizado para mantener mi marco de registro iniciado es para todas mis actividades para extender una clase base en la que sustituir onCreate y comprobar la inicialización y volver a inicializar si es necesario.

Creo que la solución oficial es usar la devolución de llamada onSaveInstanceState(Bundle b) para persistir cualquier cosa que su actividad necesite más tarde, y luego reinicializar en onCreate(Bundle b) cuando b != null .

Google lo explica mejor:

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

  • Java Singleton + clase interna mal entendida
  • Android: una vs muchas instancias de HttpClient por aplicación
  • Java.lang.IllegalStateException: La aplicación PagerAdapter cambió el contenido del adaptador sin llamar a PagerAdapter # notifyDataSetChanged android
  • ¿Es necesario guardar singletons?
  • ¿Es seguro guardar una referencia a un hilo en un singleton?
  • Ciclo de vida de singleton basado en enum en Android
  • El servicio de Android no funciona como singleton
  • Singleton en Android
  • Problemas con singleton en android
  • Cómo pasar el contexto de la aplicación dentro Singleton y SharedPreferences Clases
  • Android: ¿Cómo acceder a una sola base de datos de múltiples actividades en la aplicación?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.