Spring Boot 3.x + Webflux 多文件上传潜在内存泄漏排查求助
问题背景
将Spring Boot从2.7版本升级至3.1.0后,在文件上传场景下出现堆外内存持续增长的问题。已移除所有业务代码,仅保留最简化的Webflux文件上传控制器,压测时发起500次请求、每次上传1MB文件,发现每次测试后内存增长约7MB,但堆内存始终保持稳定,推测泄漏点在堆外内存。应用运行在K8s环境。
简化后的控制器代码如下:
@RestController @RequestMapping(value = ["/blob"]) class BlobController { // Upload blob @PutMapping(value = ["/test"], consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) fun uploadBlobAsync(@RequestPart("blob", required = true) blob: Mono<FilePart>, ): Mono<ResponseEntity<String>> { logger.info("Uploading @{}", LocalDateTime.now()) return blob .flatMap { filePart -> filePart .content() .map { dataBuffer -> try { val bytes = ByteArray(dataBuffer.readableByteCount()) dataBuffer.read(bytes) bytes } finally { DataBufferUtils.release(dataBuffer) } } .collectList() .map { _ -> ResponseEntity.ok("OK") } } } companion object { private val logger = LoggerFactory.getLogger(BlobController::class.java) } }
排查方向建议
- 开启Netty内存泄漏检测:Spring Boot 3.x默认依赖的Netty版本有更新,可通过JVM参数
-Dio.netty.leakDetectionLevel=advanced启动应用,重新压测后查看控制台是否输出内存泄漏的堆栈日志,定位未正确释放的ByteBuf来源。同时检查spring.netty.*相关配置,确认升级后Netty池化内存分配器的参数是否合理。 - 优化DataBuffer释放逻辑:虽然代码中手动调用了
DataBufferUtils.release(dataBuffer),但可尝试改用更简洁的方式确保所有DataBuffer被回收,比如:
避免手动操作字节数组时可能出现的遗漏。filePart.content() .doOnNext(DataBufferUtils::release) .collectList() - 分析堆外内存快照:在K8s容器内使用
jcmd <pid> VM.native_memory summary生成压测前后的堆外内存快照,对比Direct Buffers、Mapped Buffers及Netty相关内存区域的变化。有条件的话用async-profiler工具追踪堆外内存分配的热点代码。 - 验证版本兼容性:临时降级到Spring Boot 3.0.x版本,确认问题是否仅出现在3.1.0版本;同时查阅Spring Framework 6.0.x(Spring Boot 3.1对应版本)的变更日志,重点关注Multipart处理、Webflux文件上传模块的内存管理相关变更或修复。
- 排除K8s环境影响:在本地非容器环境复现压测场景,若问题消失,则排查K8s容器的内存限制、runtime(containerd/docker)的内存回收机制是否存在异常。
内容的提问来源于stack exchange,提问作者Bruno Dias
相关产品推荐
相关产品推荐

