静态引用被清除——如果未使用,Android 是否会在运行时卸载类?
我有一个关于类加载/垃圾收集如何在 Android 中工作的问题.我们已经多次偶然发现这个问题,据我所知,Android 在这里的行为与普通 JVM 不同.
I have a question specific to how the classloading / garbage collection works in Android. We have stumbled upon this issue a few times now, and as far as I can tell, Android behaves different here from an ordinary JVM.
问题是这样的:我们目前正在尝试减少应用程序中的单例类,转而支持单个根工厂单例,其唯一目的是管理其他管理器类.如果你愿意的话,一个顶级经理.这使我们无需选择完整的 DI 解决方案即可轻松替换测试中的实现,因为所有活动和服务共享对该根工厂的相同引用.
The problem is this: We're currently trying to cut down on singleton classes in the app in favor of a single root factory singleton which sole purpose is to manage other manager classes. A top level manager if you will. This makes it easy for us to replace implementations in tests without opting for a full DI solution, since all Activities and Services share the same reference to that root factory.
它是这样的:
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
中,即 在任何 Activity 或 Service 启动之前.现在,问题来了:getInstance
方法有时会以 null
的形式返回——即使在同一个线程上调用也是如此!听起来这不是能见度问题.相反,类级别的静态单例引用保留似乎实际上已被垃圾收集器清除.也许我在这里草率下结论,但这可能是因为 Android 垃圾收集器或类加载机制实际上可以在内存不足时 unload 类,在这种情况下,对单例实例的唯一引用将消失离开?我对 Java 的内存模型并不是很深入,但我认为这不应该发生,否则这种实现单例的常用方法在任何 JVM 上都不起作用,对吧?
initialize
is called once, in Application.onCreate
, i.e. before any Activity or Service is started. Now, here is the problem: the getInstance
method sometimes comes back as null
-- even when invoked on the same thread! That sounds like it isn't a visibility problem; instead, the static singleton reference hold on class level seems to actually have been cleared by the garbage collector. Maybe I'm jumping to conclusions here, but could this be because the Android garbage collector or class loading mechanism can actually unload classes when memory gets scarce, in which case the only reference to the singleton instance will go away? I'm not really deep into Java's memory model, but I suppose that shouldn't happen, otherwise this common way of implementing singletons wouldn't work on any JVM right?
知道为什么会这样吗?
PS:可以通过在单个应用程序实例上保留全局"引用来解决此问题.事实证明,当人们必须在应用程序的整个生命周期中始终保持对象时,这是可靠的.
PS: one can work around this by keeping "global" references on the single application instance instead. That has proven to be reliable when one must keep on object around across the entire life-time of an app.
更新
显然我在这里使用 volatile 引起了一些混乱.我的目的是确保静态引用的当前状态对所有访问它的线程始终可见.我必须这样做,因为我从多个线程中编写和读取该引用:在一个普通的应用程序中只在主应用程序线程中运行,但在一个仪器测试运行中,对象被替换为模拟,我从检测线程并在 UI 线程上读取它.我也可以同步对 getInstance
的调用,但这更昂贵,因为它需要声明对象锁.请参阅 在 Java 中实现单例模式的有效方法是什么? 对此进行更详细的讨论.
Apparently my use of volatile here caused some confusion. My intention was to ensure that the static reference's current state is always visible to all threads accessing it. I must do that because I am both writing and reading that reference from more than one thread: In an ordinary app run just in the main application thread, but in an instrumentation test run, where objects get replaced with mocks, I write it from the instrumentation thread and read it on the UI thread. I could have as well synchronized the call to getInstance
, but that's more expensive since it requires claiming an object lock. See What is an efficient way to implement a singleton pattern in Java? for a more detailed discussion around this.
推荐答案
您 (@Matthias) 和 Mark Murphy (@CommonsWare) 的说法都是正确的,但主旨似乎丢失了.(volatile
的使用是正确的,类没有被卸载.)
Both you (@Matthias) and Mark Murphy (@CommonsWare) are correct in what you say, but the gist seems lost. (The use of volatile
is correct and classes are not unloaded.)
问题的关键是从哪里调用 initialize
.
The crux of the question is where initialize
is called from.
这是我认为正在发生的事情:
Here is what I think is happening:
- 您正在从
Activity
* 调用初始化 - Android 需要更多内存,杀死整个
进程
- Android重启
Application
和最上面的Activity
- 您调用
getInstance
将返回null
,因为未调用initialize
- You are calling initialize from an
Activity
* - Android needs more memory, kills the whole
Process
- Android restarts the
Application
and the topActivity
- You call
getInstance
which will returnnull
, asinitialize
was not called
如果我错了,请纠正我.
Correct me if I'm wrong.
更新:
我的假设 - initialize
是从 Activity
* 调用的 - 在这种情况下似乎是错误的.但是,我将保留这个答案,因为这种情况是常见的错误来源.
Update:
My assumption – that initialize
is called from an Activity
* – seems to have been wrong in this case. However, I'll leave this answer up because that scenario is a common source of bugs.
相关文章