概述

自从开始接触 Spring 之后,一直以来都在思考一个问题,在 Spring 应用的运行过程中,为什么这些 bean 不会被回收?
今天深入探究了这个问题之后,才有了答案。

思考点

大家都知道,一个 bean 会不会被回收,取决于对象存活判定算法。在 JVM 底层中使用的是可达性分析算法,抛开 HotSpot 的实现细节不谈,那么一个对象被判定为死亡,应该与 GC Root 不存在可达的引用路径。

所以,Spring 的 bean 肯定是与 GC Root 存在可达的引用路径,才不会被回收掉

Java 语言对于 GC Root 的定义中,以下几种对象可以作为 GC Root

  • 虚拟机栈的栈帧中的本地变量表中,引用类型对象所指向的堆中的对象
  • 处于运行中状态(RUNNABLEBLOCKEDWAITINGTIMED_WAITING)的线程对象
  • JDK 自带的类加载器对象
  • 本地方法所引用的对象
  • JVM 持有的对象,例如基本类型的 Class 对象,NullPointerException 等常用异常对象
  • synchronized 关键字修饰的对象

一般来说,只要是符合上面这几种规则的对象,或者能由上面的规则推导出存在引用的对象,都可以作为 GC Root
那么 SpringbeanGC Root 是哪一种呢?或者说,找到了 SpringbeanGC Root,就找到了问题的答案。

动手寻找答案

首先新建一个 SpringBoot 应用,里面定义了两个 bean 以及一个启动类,包结构如下:

spring 接入 graphql_spring

然后点击运行启动类,启动完成之后,打开 jvisualVM ,找到对应的应用,然后点击生成当前dump

spring 接入 graphql_jvm_02

然后打开后选择类,输入 Hello 过滤类名,找到 HelloWorldService,点击在实例视图中显示,发现只有一个实例存在,这符合我们的预期。

最后右键点击这个实例,选择显示最近的垃圾回收根节点,可以观察到如下的引用路径:

spring 接入 graphql_静态变量_03


可以看到,DefaultListableBeanFactoryAnnotationConfigServletWebServerApplicationContext 都是我们比较熟悉的 bean 容器,对应的往下找发现有 ConcurrentHashMap$Node 引用。我们都知道在 Spring 中,正是这两个容器(准确地说是 DefaultListableBeanFactory)中使用 ConcurrentHashMap 存放了实例化好的 bean。 这都是非常符合我们预期的。

但是在 AbstractApplicationContext 再往上找后,发现有个叫 ApplicationShutdownHooks 的东西。意思就是说,我们的容器,最终与这个 ApplicationShutdownHooks 的东西扯上了引用关系。接着我们翻阅 Spring 源码进行求证:

spring 接入 graphql_java_04

发现在 AbstractApplicationContextregisterShutdownHook 方法中调用了这一行代码,而 registerShutdownHook 方法正是在 Spring 容器初始化时要调用的方法:

spring 接入 graphql_java_05

这说明在 Spring 容器初始化时,调用的这个方法,然后在继续往里跟踪这个方法:

spring 接入 graphql_静态变量_06


spring 接入 graphql_java_07


spring 接入 graphql_spring 接入 graphql_08

最后我们可以发现,AbstractApplicationContext 中的 Thread shutdownHook 变量,最终被放在了 ApplicationShutdownHooks 的这个 map 里面,而这个 map 恰好就是一个静态变量。

结论

所以,Springbean 没有被回收,正是因为在 AbstractApplicatuonContextregisterShutdownHook 方法中,与 ApplicationShutdownHooks 中的一个静态变量建立了可达的引用路径。

题外话

那么为什么类的静态变量可以作为 GC Root 呢?抱着严谨的心态,我们继续往下求证:

类的静态变量属于类对象,类对象由类加载器进行加载,而类加载器是 GC Root,那么类加载器是不是与被加载的类对象存在引用关系呢?

翻阅 ClassLoader 类,赫然看到这一段代码:

public abstract class ClassLoader {
    // The classes loaded by this class loader. The only purpose of this table
    // is to keep the classes from being GC'ed until the loader is GC'ed.
    private final Vector<Class<?>> classes = new Vector<>();
}

注释一目了然,好家伙!原来类加载器把所有的已加载的类对象都保存在这个容器里面,怪不得类对象和类静态变量也属于 GC Root