我有一个特定于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垃圾收集器或类加载机制实际上可以卸载类,在这种情况下,对单例实例的唯一引用将会发生远?我并不深入研究Java的内存模型,但我想这不应该发生,否则这种实现单例的常用方法对任何JVM都不起作用?
知道为什么会发生这种情况吗?
PS:可以通过在单个应用程序实例上保留“全局”引用来解决此问题。事实证明,当应用程序必须在应用程序的整个生命周期中保持对象时,这是可靠的。
更新
显然我在这里使用volatile会引起一些混乱。我的目的是确保静态引用的当前状态始终对访问它的所有线程可见。我必须这样做,因为我正在编写和从多个线程中读取该引用:在一个普通的应用程序中运行只是在主应用程序线程中,但在一个检测测试运行中,对象被模拟替换,我从检测线程并在UI线程上读取它。我也可以将调用同步到getInstance
,但这更昂贵,因为它需要声明对象锁定。有关此问题的详细讨论,请参阅What is an efficient way to implement a singleton pattern in Java?。
答案 0 :(得分:4)
我一生中从未见过声明为volatile
的静态数据成员。我甚至不确定这意味着什么。
静态数据成员将一直存在,直到进程终止或直到你摆脱它们为止(例如,null
静态引用)。一旦所有活动和服务被用户(例如,BACK按钮)和您的代码(例如,stopService()
)主动关闭,则该过程可以终止。如果Android在RAM上非常短暂,即使使用实时组件也可以终止该过程,但这是相当不寻常的。如果Android认为您的服务在后台服务时间过长,则可以使用实时服务终止此过程,但可能会重新启动该服务,具体取决于您onStartCommand()
的返回值。
没有卸载类,周期,没有终止进程。
为了解决@ sergui的其他问题,活动可能会被销毁,实例状态存储(虽然在RAM中,而不是“固定存储”),以释放RAM。 Android会在终止活动进程之前执行此操作,但如果它破坏了进程的最后一个活动并且没有正在运行的服务,则该进程将成为终止的主要候选者。
您的实施唯一令人感到奇怪的是您使用volatile
。
答案 1 :(得分:4)
你(@Matthias)和Mark Murphy(@CommonsWare)的说法都是正确的,但要点似乎丢失了。 (使用volatile
是正确的,并且不卸载类。)
问题的关键在于调用initialize
。
以下是我的想法:
Activity
Process
Application
和顶部Activity
getInstance
将返回null
,因为initialize
未被调用如果我错了,请纠正我。
答案 2 :(得分:2)
只要系统感觉到并且您的应用程序不是顶级(用户没有明确地运行它),就会清除静态引用。每当您的应用程序被最小化并且操作系统需要更多内存时,它将终止您的应用程序或在固定存储上将其序列化以供以后使用,但在这两种情况下都会删除静态变量。
此外,只要您的应用出现Force Close
错误,所有静态信息也会被删除。根据我的经验,我发现在Application对象中使用变量总是比静态变量更好。
答案 3 :(得分:1)
我看到类似的奇怪行为与我自己的代码涉及消失的静态变量(我不认为这个问题与volatile关键字有关)。特别是当我初始化一个日志框架(例如Crashlytics,log4j),然后经过一段时间的活动后,它似乎未初始化。调查显示操作系统调用onSaveInstanceState(Bundle b)
后会发生这种情况。
您的静态变量由应用程序进程中包含的类加载器保存。根据谷歌:
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