Spring 只用一招,就摆脱被垃圾回收的命运,拯救了自己

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根对象包括如下:

  1. 虚拟机栈引用的对象
  2. 本地方法栈内JNI(本地方法)引用的对象
  3. 方法区中类静态属性引用的对象
  4. 方法区中常量引用的对象
  5. Java虚拟机内部的引用

如果 Spring 想不被垃圾回收,那么Spring一定要确保自己被以上 GC Root引用,以上五个,任意一个即可。

接下来,我们将分析Spring 源码!找到Spring不被垃圾回收的奥秘!

启动Spring Boot应用

以下代码启动了一个Spring Boot应用,这是官方推荐的启动方式,通过注解的方式,把启动类传递给 SpringApplication ,调用run 方法,启动Spring Boot。

Spring 只用一招,就摆脱被垃圾回收的命运,拯救了自己_第1张图片

需要说明的是,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 和 Tomcat

从上面的代码可以看到 Spring Boot控制了Java应用的入口,而Web容器 Tomcat等被Spring 管理,如果Spring不会被垃圾回收,那么Tomcat就不用担心被垃圾回收。

而在Spring Boot之前的Web应用,都是将Java项目打包到Tomcat容器中执行。那时候 Spring MVC和Spring 是要被Tomcat容器管理的,所以那时的Spring项目不用担心 被垃圾回收的问题。

而现在 Tomcat和 Spring boot的角色互换,决定了 Spring Boot应用必须要处理好垃圾回收问题!

探究Spring Boot代码

创建和启动上下文

下图是 SpringApplication.run方法的源代码,run 方法主要执行两步,创建 Spring 上下文和启动上下文。 Spring 只用一招,就摆脱被垃圾回收的命运,拯救了自己_第2张图片

刷新上下文的奥秘

在Spring Boot刷新上下文的代码中,首先调用 Spring Application.refresh方法启动Spring 上下文。然后 Spring boot就把 Close 方法注册到 Java shutdownHook 关闭钩子程序中!

Spring 只用一招,就摆脱被垃圾回收的命运,拯救了自己_第3张图片基本上可以破案了!

因为Spring Boot控制了Java程序的入口,所以要负责整个项目的关闭流程,于是它 注册了Java关闭钩子。

Spring 只用一招,就摆脱被垃圾回收的命运,拯救了自己_第4张图片

接下来,我们看一下注册关闭钩子,会被 GC Root引用到吗?

关闭钩子

add 方法,将钩子程序注册到 一个容器中!

Spring 只用一招,就摆脱被垃圾回收的命运,拯救了自己_第5张图片

可以看到 Thread 类型的 钩子程序,被保存在 hooks Map 中。

而hooks列表的类型定义是 static 类型的。 static 变量都是GC Root。

Spring 只用一招,就摆脱被垃圾回收的命运,拯救了自己_第6张图片

总结

Spring Boot 在启动时会将关闭流程注册到 Java 关闭钩子中,并通过关闭钩子线程引用到 Spring 上下文。

关闭钩子会被保存在一个 static 静态类型的 Map 中,这个 Map 在 GC Root 上。

因此,Spring Boot 不被垃圾回收的关键是在启动时注册了关闭钩子。

破案了,spring永远不会被垃圾回收。

你可能感兴趣的:(java)