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处理double类型提示2e31的问题解决

    Java处理double类型提示2e31的问题解决

    本文主要介绍了Java处理double类型提示2e31的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-09-09
  • 基于SpringBoot+JWT 实现Token登录认证与登录人信息查询功能

    基于SpringBoot+JWT 实现Token登录认证与登录人信息查询功能

    本文给大家介绍基于SpringBoot+JWT实现Token登录认证与登录人信息查询功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值
    2026-03-03
  • Java获取文件后缀名的方法小结

    Java获取文件后缀名的方法小结

    在Java开发中,尤其是Web应用或文件处理场景中,获取文件后缀名是一个高频需求,无论是文件上传验证、类型过滤、格式校验,还是日志记录,后缀名的正确提取都是核心基础,本文给大家介绍了Java获取文件后缀名的方法,需要的朋友可以参考下
    2025-05-05
  • Java串口通信JSerialComm的实现

    Java串口通信JSerialComm的实现

    本文介绍了使用JSerialComm库进行Java串口通信,包括引入库的方式、设置和操作串口、发送与接收数据以及添加数据监听器,具有一定的参考价值,感兴趣的可以了解一下
    2026-01-01
  • java实现停车场系统

    java实现停车场系统

    这篇文章主要为大家详细介绍了java实现停车场系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • Java JNA库详解与本地系统交互实战记录(推荐)

    Java JNA库详解与本地系统交互实战记录(推荐)

    JNA 是一个开源库,它提供了 Java 程序调用本地共享库(如 DLLs 在 Windows 或 .so 文件在 Linux/Unix)的功能,本文给大家介绍Java JNA库详解与本地系统交互实战,感兴趣的朋友一起看看吧
    2025-11-11
  • 使用Java发送邮件到QQ邮箱的完整指南

    使用Java发送邮件到QQ邮箱的完整指南

    在现代软件开发中,邮件发送功能是一个常见的需求,无论是用户注册验证、密码重置,还是系统通知,邮件都是一种重要的通信方式,本文将详细介绍如何使用Java编写程序,实现发送邮件到QQ邮箱的功能,需要的朋友可以参考下
    2025-03-03
  • SpringBoot获取客户端的IP地址的实现示例

    SpringBoot获取客户端的IP地址的实现示例

    在Web应用程序中,获取客户端的IP地址是一项非常常见的需求,本文主要介绍了SpringBoot获取客户端的IP地址的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • SpringBoot参数校验及原理全面解析

    SpringBoot参数校验及原理全面解析

    文章介绍了SpringBoot中使用@Validated和@Valid注解进行参数校验的方法,包括基本用法和进阶用法,如自定义验证注解、多属性联合校验和嵌套校验,并简要介绍了实现原理
    2024-11-11
  • 详解Java8中的Lambda表达式

    详解Java8中的Lambda表达式

    这篇文章主要介绍了Java8中的Lambda表达式的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07

最新评论