概述
自从开始接触 Spring
之后,一直以来都在思考一个问题,在 Spring
应用的运行过程中,为什么这些 bean
不会被回收?
今天深入探究了这个问题之后,才有了答案。
思考点
大家都知道,一个 bean
会不会被回收,取决于对象存活判定算法。在 JVM
底层中使用的是可达性分析算法,抛开 HotSpot
的实现细节不谈,那么一个对象被判定为死亡,应该与 GC Root
不存在可达的引用路径。
所以,Spring 的 bean 肯定是与 GC Root 存在可达的引用路径,才不会被回收掉
在 Java
语言对于 GC Root
的定义中,以下几种对象可以作为 GC Root
:
- 虚拟机栈的栈帧中的本地变量表中,引用类型对象所指向的堆中的对象
- 处于运行中状态(
RUNNABLE
,BLOCKED
,WAITING
,TIMED_WAITING
)的线程对象 -
JDK
自带的类加载器对象 - 本地方法所引用的对象
-
JVM
持有的对象,例如基本类型的Class
对象,NullPointerException
等常用异常对象 - 被
synchronized
关键字修饰的对象
一般来说,只要是符合上面这几种规则的对象,或者能由上面的规则推导出存在引用的对象,都可以作为 GC Root
。
那么 Spring
的 bean
的 GC Root
是哪一种呢?或者说,找到了 Spring
的 bean
的 GC Root
,就找到了问题的答案。
动手寻找答案
首先新建一个 SpringBoot
应用,里面定义了两个 bean
以及一个启动类,包结构如下:
然后点击运行启动类,启动完成之后,打开 jvisualVM
,找到对应的应用,然后点击生成当前堆 dump
:
然后打开后选择类,输入 Hello
过滤类名,找到 HelloWorldService
,点击在实例视图中显示,发现只有一个实例存在,这符合我们的预期。
最后右键点击这个实例,选择显示最近的垃圾回收根节点,可以观察到如下的引用路径:
可以看到,DefaultListableBeanFactory
和 AnnotationConfigServletWebServerApplicationContext
都是我们比较熟悉的 bean
容器,对应的往下找发现有 ConcurrentHashMap$Node
引用。我们都知道在 Spring
中,正是这两个容器(准确地说是 DefaultListableBeanFactory
)中使用 ConcurrentHashMap
存放了实例化好的 bean
。 这都是非常符合我们预期的。
但是在 AbstractApplicationContext
再往上找后,发现有个叫 ApplicationShutdownHooks
的东西。意思就是说,我们的容器,最终与这个 ApplicationShutdownHooks
的东西扯上了引用关系。接着我们翻阅 Spring
源码进行求证:
发现在 AbstractApplicationContext
的 registerShutdownHook
方法中调用了这一行代码,而 registerShutdownHook
方法正是在 Spring
容器初始化时要调用的方法:
这说明在 Spring
容器初始化时,调用的这个方法,然后在继续往里跟踪这个方法:
最后我们可以发现,AbstractApplicationContext
中的 Thread shutdownHook
变量,最终被放在了 ApplicationShutdownHooks
的这个 map
里面,而这个 map
恰好就是一个静态变量。
结论
所以,Spring
的 bean
没有被回收,正是因为在 AbstractApplicatuonContext
的 registerShutdownHook
方法中,与 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