前情回顾

一探

Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗 中讲到了循环依赖问题

同样说明了 Spring 只能解决 setter 方式的循环依赖,不能解决构造方法的循环依赖

重点介绍了 Spring 是如何解决 setter 方式的循环依赖,感兴趣的可以去看下

二探

既然 Spring 不能解决构造方法的循环依赖,那么它是如何甄别构造方法循环依赖的了?

所以进行了二探:再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的?

从源码的角度讲述了 Spring 是如何判定构造方法循环依赖、原型循环依赖的

感兴趣的可以去看下

大家跟源码的时候,一定要注意版本!!!

项目模拟

自认为经过了前两探,对 Spring 循环依赖的问题已了若指掌,可面对线上突如其来的循环依赖问题,楼主竟然没能一眼看出来!!!

这楼主能忍?于是楼主又跟起了 Spring 源码,看看问题到底出在哪?

SpringBoot 版本是 2.0.3.RELEASE

线上服务采用 k8s 部署,本地环境未采用 k8s 部署

本地启动从未出现循环依赖问题,线上环境也只是偶发的 pod 启动失败(提示信息直指循环依赖)

问题偶发,而非必现,很是头疼,但问题还是得解决,从提示信息着手呗

根据错误提示信息,楼主模拟出了一个简化的工程,方便我们进行问题排查

图片

非常简单,完整地址:spring-other-circular-reference

我们来看下类图

图片

MyListener 、 MyService 、 MyManager 很常规,特殊的是 MyConfig 和 MySender

图片

图片

问题复现

如果按上述工程结构,本地很难复现问题 ,反正楼主是没复现出来

我们稍做调整,将 MySender 前置,如下

图片

启动失败,错误信息如下:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myConfig': Unsatisfied dependency expressed through field 'myListener'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myListener': Unsatisfied dependency expressed through field 'myService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myServiceImpl': Unsatisfied dependency expressed through field 'myManager'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myManager': Unsatisfied dependency expressed through field 'mySender'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mySender': Requested bean is currently in creation: Is there an unresolvable circular reference?

此刻的 Is there an unresolvable circular reference? 让楼主感到了陌生

我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取!

问题分析

我们从以下几个方面来分析

BeanDefinition 扫描

目前 XML 方式的 Bean 定义越来越少,除了一些遗留的老项目,基本看不到 XML 方式的 Bean 定义了

所以我们只关注注解方式的 Bean 定义的扫描

文件夹的扫描顺序与文件夹名字的升序一致,文件的顺序与文件名的升序一致,如下所示

图片

有兴趣的可以去跟下 ConfigurationClassParser 类中 doProcessConfigurationClass 方法;楼主做了下简单的总结

图片

@ComponentScan 的处理早于 @Bean

BeanDefinition 扫描过程中,会按扫描顺序会往 DefaultListableBeanFactory 的 beanDefinitionMap 中添加 BeanDefinition ,往 beanDefinitionNames 添加 BeanName

我们来跟下源码,看是不是如上所说

图片

先被扫描的 BeanDefinition 的 BeanName 会被先添加到 beanDefinitionNames

BeanDefinition 覆盖

MyConfig 中通过 @Bean 定义了 MySender ,而 MySender 类上又用了 @Component 进行修饰

那创建 MySender 实例的时候到底调用的哪个构造方法?(有参还是无参?)

关于 Spring Boot 中创建对象的疑虑 → @Bean 与 @Component 同时作用同一个类,会怎么样?从源码的角度分析了这个问题

结论是:SpringBoot 2.0.3.RELEASE 中, @Configuration + @Bean 修饰的 BeanDefinition 会覆盖掉 @Component 修饰的 BeanDefinition

也就说 MySender 类上的 @Component 其实没用,加不加效果是一样的,这里说的 没用效果 仅仅指的是 MySender 的 BeanDefinition

Bean 实例化顺序

BeanDefinition 用来构建实例,那么 MySender 上的 @Component 就有作用了,它决定了 MySender 的实例化顺序

是先于 MyConfig 、 MyListener 、 MyServiceImpl 、 MyManager 实例化的

我们来看下 Bean 的实例化顺序

图片

理论上来讲,先被扫描的 Bean 会先被实例化;Bean 实例化的过程中会填充属性,可能会导致后被扫描的 Bean 提前被实例化

如果 Bean 之间没有依赖,那么会严格按照 Bean 的扫描顺序实例化

再看问题

我们再回到前面的问题

图片

这种情况下,我们分析下 Is there an unresolvable circular reference? 是如何产生的

相较于 MyConfig 、 MyListener 、 MyManager 、 MyServiceImpl , MySender 是最先被扫描到的,所以它最先被实例化

因为 MyConfig 中通过 @Bean 修饰了 MySender 的 BeanDefinition

图片

会覆盖掉 MySender 自身的无参 BeanDefinition

所以会通过 MySender 的有参构造方法来创建 MySender 实例

因为有参构造方法依赖 myListener ,所以去 Spring 容器中找 MyListener 实例,没有找到则创建,然后填充 MyListener 实例的属性

以此类推,实例的创建过程如下所示:

图片

Is there an unresolvable circular reference?就此产生

相当于是变种的构造方法循环依赖

我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取!

最初状态

我们还原 MySender 位置

图片

此时最先实例化的是 MyConfig ,实例化过程如下

图片

对象是都可以正常实例化、初始化的

这种情况理论上来讲是不会出现 Is there an unresolvable circular reference?

线上问题

一通分析下来,还是没能找到线上 Is there an unresolvable circular reference?的原因

很是尴尬,但是我萌生了这样的想法:是不是在 k8s 部署过程中, BeanDefinition 的扫描会有偶发的随机性?

问题修复

虽然我们没能找到线上问题的确切原因,但还是有办法去根治这个问题的

Spring 不能处理构造方法循环依赖,那我们就去规避它

删掉 MyConfig , MySender 改成

图片

或 MySender 改成

图片

还有 @PostConstruct 等,方式有很多,只要不产生构造方法循环依赖就好

总结

1、 BeanDefinition 扫描顺序

如果我们去跟源代码就会发现,以启动类为起点,扫描启动类同级目录下的所有文件夹

按文件夹名升序顺序进行扫描,会递归扫描每个文件夹

文件扫描也是按文件名升序顺序进行

从线上问题来看,对这个扫描顺序,楼主是持怀疑态度的:是 Spring 会偶发的随机扫描,还是 pod 会导致偶发的随机扫描

2、 BeanDefinition 覆盖

只要我们读了源码,了解 Spring 对各个注解的扫描顺序,就清楚它们的替换关系了

BeanDefinition 覆盖并不会影响 BeanDefinition 的扫描顺序

也就是不会改变 BeanName 在 beanDefinitionNames 中的位置,即不会影响 Bean 的示例化顺序

3、 Bean 实例化顺序

理论上来讲,先被扫描到的就先被实例化,但实例化过程中的属性填充会打乱这个顺序,会将被依赖的对象提前实例化

4、 Spring 版本

一定要结合版本来看问题

版本不同,底层实现可能会不同

转自:青石路

链接: https://www.cnblogs.com/youzhibing/p/15835048.html

我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取!

6 垃圾收集算法

ZGC 采用标记 - 整理算法,算法的思想是把所有存活对象移动到堆的一侧,移动完成后回收掉边界以外的对象。如下图:

图片

6.1 JDK 16 之前

在 JDK 16 之前,ZGC 会预留(Reserve)一块儿堆内存,这个预留内存不能用于 Java 线程的内存分配。即使从 Java 线程的角度看堆内存已经满了也不能使用 Reserve,只有 GC 过程中搬移存活对象的时候才可以使用。如下图:

图片

这样做的好处是算法简单,非常适合并行收集。但这样做有几个问题:

  • 因为有预留内存,能给 Java 线程分配的堆内存小于 JVM 声明的堆内存。

  • Reserve 仅仅用于存放 GC 过程中搬移的对象,有点内存浪费。

  • 因为 Reserve 不能给 GC 过程中搬移对象的 Java 线程使用,搬移线程可能会因为申请不到足够内存而不能完成对象搬移,这返回过来又会导致应用程序的 OOM。

6.2 JDK 16 改进

JDK 16 发布后,ZGC 支持就地搬移对象(G1 在 Full GC 的时候也是就地搬移)。这样做的好处是不用预留空闲内存了。如下图:

图片

不过就地搬移也有一定的挑战。比如:必须考虑搬移对象的顺序,否则可能会覆盖尚未移动的对象。这就需要 GC 线程之间更好的进行协作,不利于并发收集,同时也会导致搬移对象的 Java 线程需要考虑什么可以做什么不可以做。

为了获得更好的 GC 表现,JDK 16 在支持就地搬移的同时,也支持预留(Reserve)堆内存的方式,并且 ZGC 不需要真的预留空闲的堆内存。默认情况下,只要有空闲的 region,ZGC 就会使用预留堆内存的方式,如果没有空闲的 region,否则 ZGC 就会启用就地搬移。如果有了空闲的 region, ZGC 又会切换到预留堆内存的搬移方式。

7 总结

内存多重映射和染色指针的引入,使 ZGC 的并发性能大幅度提升。

ZGC 只有 3 个需要 STW 的阶段,其中初始标记和初始转移只需要扫描所有 GC Roots,STW 时间 GC Roots 的数量成正比,不会耗费太多时间。再标记过程主要处理并发标记引用地址发生变化的对象,这些对象数量比较少,耗时非常短。可见整个 ZGC 的 STW 时间几乎只跟 GC Roots 数量有关系,不会随着堆大小和对象数量的变化而变化。

ZGC 也有一个缺点,就是浮动垃圾。因为 ZGC 没有分代概念,虽然 ZGC 的 STW 时间在 1ms 以内,但是 ZGC 的整个执行过程耗时还是挺长的。在这个过程中 Java 线程可能会创建大量的新对象,这些对象会成为浮动垃圾,只能等下次 GC 的时候进行回收。

参考

1.https://wiki.openjdk.java.net/display/zgc 2.https://openjdk.java.net/jeps/304 3.https://openjdk.java.net/jeps/376 4.https://malloc.se/blog/zgc-jdk16 5.https://mp.weixin.qq.com/s/ag5u2EPObx7bZr7hkcrOTg 6.https://mp.weixin.qq.com/s/FIr6r2dcrm1pqZj5Bubbmw 7.https://www.jianshu.com/p/664e4da05b2c 8.https://www.cnblogs.com/jimoer/p/13170249.html 9.https://www.jianshu.com/p/12544c0ad5c1

推荐阅读

  • 脸都不要了!直接拷贝人家PPT要融资?

  • 这个“通用控制”功能太好用了!赶紧升级吧!

  • 被315曝光还霸气回应“不受影响”?

··································

你好,我是程序猿DD,10年开发老司机、阿里云MVP、腾讯云TVP、出过书、创过业、国企4年互联网6年。10年前毕业加入宇宙行,工资不高、也不算太忙,业余坚持研究技术和做自己想做的东西。4年后离开国企,加入永辉互联网板块的创业团队,从开发、到架构、到合伙人。一路过来,给我最深的感受就是一定要不断学习并关注前沿。只要你能坚持下来,多思考、少抱怨、勤动手,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你看好一个事情,一定是坚持了才能看到希望,而不是看到希望才去坚持。相信我,只要坚持下来,你一定比现在更好!如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯,帮你积累弯道超车的资本。

点击阅读原文,领取2022最新10000T学习资料

记一次线上偶现的循环依赖问题相关推荐

  1. 记一次线上coredump事故

    转自:http://www.likecs.com/show-16439.html 记一次线上coredump事故 1.事故背景 上周三凌晨,我负责的某个模块在多台机器上连续发生coredump,幸好发 ...

  2. 记一次线上环境 redis偶尔连接超时报错 解决

    记一次线上环境 redis偶尔连接超时报错 解决 贴出本地控制台日志 说实话,很痛苦,跟进很久了,一直认为的jvm程序所使用的配置的连接池框架问题 因为程序为 springboot 2 spring ...

  3. 记几次 [线上环境] Dubbo 线程池占满原因分析(第三次:GC STW)

    [线上环境] Dubbo 线程池占满原因排查系列 记几次 [线上环境] Dubbo 线程池占满原因分析(第一次:HttpClient) 记几次 [线上环境] Dubbo 线程池占满原因分析(第二次:C ...

  4. 记一次线上应用连接池满的处理

    记一次线上应用dubbo-claim连接池满的处理 首先看到dubbo-claim应用突然大面积报错,基本反馈是冻结预算出问题了,看了看冻结预算的代码,发现写的非常复杂,果断放弃看代码来排查问题. C ...

  5. 记一次线上商城系统 Tomcat、JVM 高并发的优化

    来源:https://urlify.cn/jyYny2 对于线上系统调优,它本身是个技术活,不仅需要很强的技术实战能力,很强的问题定位,问题识别,问题排查能力,还需要很丰富的调优能力. 本篇文章从实战 ...

  6. 【MySQL】记一次线上重大事故:二狗子竟然把线上数据库删了!!

    写在前面 估计二狗子这几天是大姨夫来了,心情很郁闷,情绪也很低落,工作的时候也有点心不在焉.让他发个版本,结果,一行命令下去把线上的数据库删了!你没听错:是删掉了线上的数据库!运营那边顿时炸了锅:怎么 ...

  7. java mysql死锁_记一次线上mysql死锁分析(一)

    记录一次比较诡异的mysql死锁日志.系统运行几个月来,就在前几天发生了一次死锁,而且就只发生了一次死锁,整个排查过程耗时将近一天,最后感谢我们的DBA大神和老大一起分析找到原因. 诊断死锁 借助于我 ...

  8. 记一次线上商城系统高并发的优化

    对于线上系统调优,它本身是个技术活,不仅需要很强的技术实战能力,很强的问题定位,问题识别,问题排查能力,还需要很丰富的调优能力. 本篇文章从实战角度,从问题识别,问题定位,问题分析,提出解决方案,实施 ...

  9. 记一次线上cpu飙升100%的排查过程

    大家好,我是烤鸭: 最近没怎么写技术文章,还是得回归下初心,正好前几天出现个线上问题,记录下排查过程. 问题描述 某个时间点,接收到接口响应慢报警. 过一会收到服务器cpu可用率低(<10%)报 ...

最新文章

  1. java培训:Java的十大算法
  2. oracle+cast函数+长度,oracle cast() 函数问题
  3. python垃圾回收机制原理_如何理解和掌握Python垃圾回收机制?
  4. php获取虚拟机ip,php如何获取用户的ip地址
  5. Sharepoint项目周记一:关于开展MOSS项目的前期调查和需要解决的问题
  6. CAP 理论、BASE 理论、FLP 理论
  7. 新工科背景下大数据专业导论课程的改革与探索
  8. Timestamp 与 Date 变量绑定与Oracle的自动分区
  9. Youki的Matlab命名规则
  10. 人工智能建立本体库_本体:一文读懂领域本体构建
  11. 主帅的一道坎?亚洲杯至今已有九位主帅离任
  12. yolov3视频检测图像上显示准确率
  13. 用C/C++实现SMC动态代码加密技术 .
  14. STM32读取AD芯片ADS1110数据
  15. C++ 模板(泛型)
  16. Balsamiq Mockups注册码
  17. 基于莱维飞行和随机游动策略的灰狼算法-附代码
  18. 雷军:我也想做高级工程师 !
  19. H5页面 禁止微信分享转发按钮
  20. Adaboost算法的理解

热门文章

  1. lighttpd缓存配置
  2. Nginx的proxy_cache缓存功能
  3. python3 浮点型 字符串 整形 互转实例
  4. linux make 命令行 定义宏(-D)传递给C源代码 简介
  5. python 全局变量 局部变量
  6. golang 判断 数组 切片 是否为空
  7. linux系统利用可执行文件的Capabilities实现权限提升
  8. python3 异步 semaphore 信号量 控制并发
  9. linux iostat 查看磁盘io利用率
  10. linux 禁用 ctrl+alt+del 重启系统