Spring bean不被GC的真正原因及分析

 更新时间:2024年04月29日 10:06:35   作者:Bryant5051  
这篇文章主要介绍了Spring bean不被GC的真正原因及分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

概述

自从开始接触 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

那么 SpringbeanGC Root 是哪一种呢?或者说,找到了 SpringbeanGC Root,就找到了问题的答案。

动手寻找答案

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

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

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

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

可以看到,DefaultListableBeanFactoryAnnotationConfigServletWebServerApplicationContext 都是我们比较熟悉的 bean 容器,对应的往下找发现有 ConcurrentHashMap$Node 引用。

我们都知道在 Spring 中,正是这两个容器(准确地说是 DefaultListableBeanFactory)中使用 ConcurrentHashMap 存放了实例化好的 bean。 这都是非常符合我们预期的。

但是在 AbstractApplicationContext 再往上找后,发现有个叫 ApplicationShutdownHooks 的东西。意思就是说,我们的容器,最终与这个 ApplicationShutdownHooks 的东西扯上了引用关系。接

着我们翻阅 Spring 源码进行求证:

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

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

最后我们可以发现,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

最后

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java语言描述Redis分布式锁的正确实现方式

    java语言描述Redis分布式锁的正确实现方式

    这篇文章主要介绍了java语言描述Redis分布式锁的正确实现方式,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • 这一次搞懂Spring代理创建及AOP链式调用过程操作

    这一次搞懂Spring代理创建及AOP链式调用过程操作

    这篇文章主要介绍了这一次搞懂Spring代理创建及AOP链式调用过程操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • IDEA + Maven环境下的SSM框架整合及搭建过程

    IDEA + Maven环境下的SSM框架整合及搭建过程

    这篇文章主要介绍了IDEA + Maven环境下的SSM框架整合及搭建过程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-01-01
  • Java中关于优先队列PriorityQueue的使用及相关方法

    Java中关于优先队列PriorityQueue的使用及相关方法

    这篇文章主要介绍了Java中关于优先队列PriorityQueue的使用及相关方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • 基于springBoot配置文件properties和yml中数组的写法

    基于springBoot配置文件properties和yml中数组的写法

    这篇文章主要介绍了springBoot配置文件properties和yml中数组的写法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java Mybatis一级缓存和二级缓存

    Java Mybatis一级缓存和二级缓存

    缓存是内存当中一块存储数据的区域,目的是提高查询效率,降低服务器和数据库的压力,这篇文章主要介绍了Mybatis一级缓存和二级缓存,感兴趣的同学可以参考阅读本文
    2023-04-04
  • 2020最新版SSM框架整合教程

    2020最新版SSM框架整合教程

    这篇文章主要介绍了2020最新版SSM框架整合教程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 多方面解读Java中的volatile关键字

    多方面解读Java中的volatile关键字

    这篇文章主要介绍了多方面解读Java中的volatile关键字,它的作用是强制对被修饰的变量的写操作立即刷新到主存中,并强制对该变量的读操作从主存中读取最新的值,而不是使用缓存中的值,需要的朋友可以参考下
    2023-05-05
  • Java实现线程插队的示例代码

    Java实现线程插队的示例代码

    在编写多线程的业务时,会遇到让一个线程优先于其他线程运行的情况,除了可以设置线程的优先级高于其他线程,还有更直接的方式:线程插队。本文将用Java实现线程插队,需要的可以参考一下
    2022-08-08
  • Java获取文件的路径及常见问题解决方案

    Java获取文件的路径及常见问题解决方案

    这篇文章主要介绍了Java获取文件的路径及常见问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03

最新评论