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。

@RestController
@SpringBootApplication
public class MyApplication {
    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

需求说明的是,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 上下文和发动上下文。

Springboot怎么解救自己,才干防止被废物收回的命运

刷新上下文的奥秘

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

Springboot怎么解救自己,才干防止被废物收回的命运
基本上可以破案了!

由于Spring Boot操控了Java程序的进口,所以要负责整个项目的封闭流程,于是它 注册了Java封闭钩子。

Springboot怎么解救自己,才干防止被废物收回的命运

接下来,咱们看一下注册封闭钩子,会被 GC Root引证到吗?

封闭钩子

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

Springboot怎么解救自己,才干防止被废物收回的命运

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

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

Springboot怎么解救自己,才干防止被废物收回的命运

总结

Spring Boot 在发动时会将封闭流程注册到 Java 封闭钩子中,并经过封闭钩子线程引证到 Spring 上下文。

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

因而,Spring Boot 不被废物收回的关键是在发动时注册了封闭钩子。

破案了,spring永远不会被废物收回。