最近开发的时候,使用了一个单例模式,当我返回键退出App,再重新启动,发现App的确是从首页启动,但还没有执行为单例类设置数值的位置。可是!断点调试的时候发现这时候已经有了一个数据,并且是上一次运行留下的数据,当时觉得很神奇,明明根Activity已经执行了OnDestroy(),而且再启动的确是从首页过来的,讲道理应该是“everything will be new”,但是单例里面的数据仍然存在,这可是为什么呢?

Google搜了一下,噢!恍然大悟,看到这一块的知识很久没有用就忘掉了,或者说对运行机制相关还不太熟悉吧,所以在此重新整理总结一份。

返回键退出和直接杀死进程退出的区别?

直接杀死退出:所有的内存都会被回收,重新启动应用程序时,会重新调用Application的OnCreate()方法,会调用onSaveInstanceState方法。

返回键退出程序:退出程序后,一些加载过的静态变量并没有被回收,重新启动也不需要调用Application的OnCreate()方法。

于是我们就知道,静态变量并没有被回收,而我们的单例模式实例就是静态变量,没有被回收,于是我们就知道为什么单例模式数据还存在了,于是在响应的位置对其数据进行释放。可是 why?这两者的差异究竟是什么导致的?我们要知其然,也要知其所以然。

关于直接杀死进程

这里我们应该很好去理解,Android中的每一个App都是运行在自己VM实例之中(沙盒)。每一个VM实例在linux中又是一个单独的进程,通过任务管理杀掉一个进程,那么对应进程里面的数据全部被回收掉。

关于返回键退出

通过对源码的追溯,如果不对onBackPressed()做特殊的处理,无论是AppCompatActivity还是android.app.Activity,发现都会通过执行onBackPressed(),最后到Activity的finish()方法,也就是说当App退出到根的时候,最终只是执行的是当前App根Activity的finish()方法,整个App“依然在运行”,只是看不到界面了,那么也就是说,如果在App中运行的Service之类的后台任务并没结束,仍然在运行。

那为什么单例模里面的静态变量没有回收呢?如果问你的话,你怎么答?emmmmm……因为……它没有被销毁嘛,所以它还在。当然不能这么回答了,需要用理论依据来解释。

关于方法区与静态变量

我们知道静态变量存在与JVM的方法区中,静态变量在类被加载的时候分配内存,Java虚拟机规范中说过可以不要求虚拟机在方法区实现垃圾收集,如下图所示:

image-20200908230104508

那么我们是不是可以理解为方法区中不会进行垃圾回收?查到来自《深入理解Java虚拟机》中的解释:

很多人以为方法区(或者HotSopt 虚拟机中的永久代)是没有垃圾收集的,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且性价比一般较低,在对的新生代生一般能回收70%~95%的空间,而永久代远低于此。

永久代的垃圾手机主要回收两部分内容:废弃常量无用的类。 回收废弃常量与回收Java堆中的对象非常相似。以常量池中字面量的回收为例,若字符串“abc”已经进入常量池中,但当前系统没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用该字面量,若发生内存回收,且必要的话,该“abc”就会被系统清理出常量池。常量池中其他的类(接口)、方法、字段的符号引用与此类似。

无用的类需要满足3个条件:

(1)该类所有的实例都已经被回收,即Java堆中不存在该类的任何实例;
(2)加载该类的ClassLoader已经被回收;
(3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

那么对于我们的静态变量来说,如果不是我们手动处理的话设置实例为null的话,或其他操作的话,那么就不会满足上面的条件。那么静态变量会在什么时候被销毁呢?答案很简单了就:静态变量在类被卸载的时候销毁,类在什么时候被卸载?在进程结束的时候。那么这也自然能解释我最开始遇到的情况了,返回键返回结束App后进程并没有结束,当下一次再启动App的时候,进程并没有销毁而,因是同一个进程,所以单例中的数据依然存在。