集成测试使用@DirtiesContext时Spring DefaultListableBeanFactory内存泄漏咨询
你的猜想完全正确,@TestInstance(TestInstance.Lifecycle.PER_CLASS)与@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)的组合和观测到的内存泄漏现象存在直接关联,具体原理如下:
DefaultListableBeanFactory是Spring上下文的核心容器,每个独立的Spring测试上下文都会对应一个独有的实例,内部持有所有托管Bean、配置元数据的引用,本身内存占用较高。你通过Eclipse MAT得到的堆分析结果也验证了该对象是内存占用的主要来源,对应分析截图如下:
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)的作用是标记当前测试类关联的Spring上下文为「脏上下文」,仅在整个测试类的所有测试用例全部执行完成后,才会触发上下文销毁逻辑。在整个测试类的执行周期内,对应的DefaultListableBeanFactory会一直被强引用持有,无法被GC回收。@TestInstance(TestInstance.Lifecycle.PER_CLASS)是JUnit 5的测试实例生命周期配置,默认的PER_METHOD模式下每个测试用例都会创建一个新的测试类实例,用例执行完成后实例即可被回收。而PER_CLASS模式下整个测试类只会创建一个测试实例,该实例会持有Spring上下文的引用,进一步延长了上下文的存活周期,加大了GC回收的门槛。- 批量运行测试时,多个测试类会先后加载各自的Spring上下文,在每个测试类完全执行完成前,对应的
DefaultListableBeanFactory都会存活在堆内存中,内存占用随测试用例数量逐步累积,超过256M堆上限就会抛出OOM。将堆内存提升到1G只是提高了可同时容纳的上下文数量,仅能推迟OOM出现,没有解决根本的内存累积问题。
涉及的测试注解代码如下:
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
优化方案
- 优先移除不必要的
@DirtiesContext注解:如果多个测试类的Spring配置完全一致,Spring测试框架默认会自动复用同一个上下文,不会重复创建DefaultListableBeanFactory,可以大幅降低内存占用。仅当测试用例会修改上下文配置(比如修改Bean属性、修改嵌入式数据库数据)的时候才需要使用@DirtiesContext。 - 调整
@DirtiesContext的清理时机:如果确实需要标记脏上下文,可以根据业务场景将classMode调整为DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD,每个测试用例执行完成后就清理上下文,避免内存长时间累积。 - 调整Maven Failsafe插件的Fork配置:设置
forkCount参数开启多进程执行测试,同时配置reuseForks=false,每执行一个测试类就重启Fork进程,直接清空整个堆内存,从根本上避免内存累积。 - 验证上下文销毁逻辑:确认de.flapdoodle嵌入式MongoDB的相关Bean在上下文销毁时会正确关闭实例、释放资源,避免出现自定义Bean持有上下文引用导致
DefaultListableBeanFactory无法被GC的问题。
内容的提问来源于stack exchange,提问作者Manuel Waltschek
相关产品推荐
相关产品推荐

