Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/202.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 静态引用被清除——如果未使用,Android是否在运行时卸载类?_Java_Android_Garbage Collection_Singleton_Classloader - Fatal编程技术网

Java 静态引用被清除——如果未使用,Android是否在运行时卸载类?

Java 静态引用被清除——如果未使用,Android是否在运行时卸载类?,java,android,garbage-collection,singleton,classloader,Java,Android,Garbage Collection,Singleton,Classloader,我有一个关于类加载/垃圾收集在Android中如何工作的问题。我们已经多次偶然发现这个问题,据我所知,Android在这里的行为与普通JVM不同 问题是:我们目前正试图减少应用程序中的单例类,而采用单根工厂单例,其唯一目的是管理其他管理器类。如果你愿意的话,请找一位高层经理。这使得我们可以轻松地替换测试中的实现,而无需选择完整的DI解决方案,因为所有活动和服务都共享对该根工厂的相同引用 下面是它的样子: public class RootFactory { private static

我有一个关于类加载/垃圾收集在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
应用程序中调用一次。onCreate
,即在启动任何活动或服务之前。现在,问题来了:
getInstance
方法有时返回为
null
——即使在同一线程上调用也是如此!这听起来好像不是一个可见性问题;相反,类级别上的静态单例引用保持似乎实际上已被垃圾收集器清除。也许我在这里想得出结论,但这可能是因为Android垃圾收集器或类加载机制实际上可以在内存不足时卸载类,在这种情况下,对singleton实例的唯一引用将消失?我对Java的内存模型并没有深入了解,但我认为这不应该发生,否则这种实现单例的常见方法在任何JVM上都不起作用,对吗

你知道为什么会这样吗

PS:可以通过在单个应用程序实例上保留“全局”引用来解决这个问题。这已经被证明是可靠的,当一个人必须在应用程序的整个生命周期中保持对象

更新


显然我在这里使用volatile引起了一些混乱。我的意图是确保静态引用的当前状态对访问它的所有线程始终可见。我必须这样做,因为我正在从多个线程写入和读取引用:在一个普通应用程序中,只在主应用程序线程中运行,但在一个插装测试运行中,当对象被替换为mock时,我从插装线程写入,然后在UI线程上读取。我也可以将调用同步到
getInstance
,但这更昂贵,因为它需要声明对象锁。有关这方面的详细讨论,请参阅。

只要系统感觉像静态引用,并且您的应用程序不是顶级的(用户没有显式运行它),就会清除静态引用。每当你的应用程序被最小化,并且操作系统需要更多的内存时,它要么会杀死你的应用程序,要么将其序列化到固定存储上供以后使用,但在这两种情况下,静态变量都会被擦除。
此外,每当你的应用程序出现
强制关闭
错误时,所有静态数据都会被删除。根据我的经验,在应用程序对象中使用变量总是比使用静态变量更好

我一生中从未见过声明为volatile的静态数据成员。我甚至不知道那是什么意思

静态数据成员将一直存在,直到进程终止或您除去它们(例如,
null
out静态引用)。一旦用户(例如,后退按钮)和您的代码(例如,
stopService()
)主动关闭所有活动和服务,流程可能会终止。如果Android在RAM上极度短缺,即使使用了活动组件,进程也可能终止,但这是相当不寻常的。如果Android认为您的服务在后台的时间太长,则可能会使用实时服务终止该过程,不过它可能会根据您从
onStartCommand()
返回的值重新启动该服务

在进程终止之前,类不会被卸载

为了解决@sergui的另一个问题,可以销毁活动,并存储实例状态(尽管是在RAM中,而不是“固定存储”),以释放RAM。Android会在终止活动进程之前这样做,尽管如果它破坏了进程的最后一个活动,并且没有运行的服务,那么该进程将是终止的主要候选

关于您的实现,唯一值得注意的奇怪之处是您使用了
volatile

您(@Matthias)和Mark Murphy(@commonware)所说的都是正确的,但是要点似乎没有了。(使用
volatile
是正确的,并且未卸载类。)

问题的关键是从哪里调用
initialize

以下是我认为正在发生的事情:

  • 您正在从
    活动调用initialize
    *
  • 安卓需要更多的内存,扼杀了整个
    进程
  • Android重新启动
    应用程序
    和顶部的
    活动
  • 调用
    getInstance
    ,它将返回
    null
    ,因为没有调用
    initialize
如果我错了,请纠正我


更新:

在这种情况下,我的假设(即
初始化
是从
活动
*调用的)似乎是错误的。但是,我将保留这个答案,因为这种情况是常见的bug源。

我在自己的代码中也看到过类似的奇怪行为,涉及到静态变量的消失(我认为这个问题与volatile关键字无关)。特别是当我初始化了一个日志框架(比如Crashlytics,log4j)之后,在一段时间的活动之后,它似乎没有初始化。调查显示,这发生在操作系统调用saveInstanceState(Bundle b)之后

静态变量由类加载器持有,类加载器包含在应用程序的进程中