SpringBoot ApplicationContext 会被 JVM 当成垃圾对象,然后回收掉吗?
最近五阳哥在复习JVM 垃圾回收的知识,被别人问到这个问题,我心里感到一惊,如果Spring 被回收掉,Spring管理的bean全部会被回收,那我们的Java应用不就被一锅端了吗? 这太可怕了……
虽然我相信 Spring一定会处理好这个问题,确保自身不被垃圾回收,但是巨大的好奇心,驱使我一探究竟。
我们在岁月静好时,Spring帮我们做了哪些事情?
精通Java GC的可以跳过这一段。
五阳哥,前两天写了一篇文章,分析为什么gc一定需要Stop the world? # 点击查看,这篇文章? ,里面已经总结道,“当一个对象无法被 GC Root引用到,那么这个对象将在接下来的垃圾回收过程中被回收”。
在Java堆中,存放着所有Java的对象实例。在进行垃圾收集之前,JVM需要确定哪些对象已经不被使用(即垃圾),哪些对象仍然被使用。为了判断对象是否是“垃圾”,JVM采用了可达性分析算法。
可达性分析算法 是指通过指定 GC Root 根对象,从根对象开始搜索引用的对象,通过引用链条,层层遍历链条上的对象,可以到达的对象不可被垃圾回收。而最终没有被搜索遍历到的对象,则为 不可达对象,应该被垃圾回收。
JVM中的 GC Root根对象包括如下:
如果 Spring 想不被垃圾回收,那么Spring一定要确保自己被以上 GC Root引用,以上五个,任意一个即可。
接下来,我们将分析Spring 源码!找到Spring不被垃圾回收的奥秘!
以下代码启动了一个Spring Boot应用,这是官方推荐的启动方式,通过注解的方式,把启动类传递给 SpringApplication ,调用run 方法,启动Spring Boot。
需要说明的是,run方法在Spring boot启动成功后,会立即返回,不会被阻塞。所以 main 线程在启动Spring boot后,将退出……
由于Spring Boot会启动Jetty/Tomcat等其他线程池,所以Java应用并不会退出。因为Java进程退出条件之一是:所有非守护线程全部退出,则JVM退出 点击查看 JVM 如何退出的详细信息 ,所以只有 main 线程退出,其他业务线程还存在情况下,Java不会退出。
由此可见,main 线程在启动 Spring Boot后,并不会一直持有 Spring boot 对象引用,官方文档里也没有 强调,一定要保持 main 线程 不退出。Spring Boot需要把自己交到其他对象手中,确保自己不被回收!
那么如何保证 Spring Boot不被垃圾回收呢?我需要从 SpringApplcaition 内部找原因!
从上面的代码可以看到 Spring Boot控制了Java应用的入口,而Web容器 Tomcat等被Spring 管理,如果Spring不会被垃圾回收,那么Tomcat就不用担心被垃圾回收。
而在Spring Boot之前的Web应用,都是将Java项目打包到Tomcat容器中执行。那时候 Spring MVC和Spring 是要被Tomcat容器管理的,所以那时的Spring项目不用担心 被垃圾回收的问题。
而现在 Tomcat和 Spring boot的角色互换,决定了 Spring Boot应用必须要处理好垃圾回收问题!
下图是 SpringApplication.run方法的源代码,run 方法主要执行两步,创建 Spring 上下文和启动上下文。
在Spring Boot刷新上下文的代码中,首先调用 Spring Application.refresh方法启动Spring 上下文。然后 Spring boot就把 Close 方法注册到 Java shutdownHook 关闭钩子程序中!
因为Spring Boot控制了Java程序的入口,所以要负责整个项目的关闭流程,于是它 注册了Java关闭钩子。
接下来,我们看一下注册关闭钩子,会被 GC Root引用到吗?
add 方法,将钩子程序注册到 一个容器中!
可以看到 Thread 类型的 钩子程序,被保存在 hooks Map 中。
而hooks列表的类型定义是 static 类型的。 static 变量都是GC Root。
Spring Boot 在启动时会将关闭流程注册到 Java 关闭钩子中,并通过关闭钩子线程引用到 Spring 上下文。
关闭钩子会被保存在一个 static 静态类型的 Map 中,这个 Map 在 GC Root 上。
因此,Spring Boot 不被垃圾回收的关键是在启动时注册了关闭钩子。
破案了,spring永远不会被垃圾回收。