ASP.NET应用上传文件时内存占用过高且无法回收的问题
ASP.NET文件上传接口内存异常增长排查:UploadFileAsync内存无法释放但UploadStringAsync正常
问题描述
排查ASP.NET应用文件上传内存泄漏问题时,编写了如下测试控制器:
[ApiController] [Route("[controller]")] public class MoqController : ControllerBase { [HttpPost()] [Route("UploadString")] public async Task<IActionResult> UploadStringAsync(string str) { return Ok(str); } [HttpPost()] [Route("UploadFile")] public async Task<IActionResult> UploadFileAsync(IFormFile uploadedFile) { return Ok(); } }
测试发现:
- 调用
UploadFileAsync接口时,应用内存持续上升,垃圾回收器无法释放,每次请求内存增加1-3MB,且增量与上传文件大小关联性弱; - 调用
UploadStringAsync接口则无此异常。
原因分析
- IFormFile绑定的底层资源未被触发清理:ASP.NET Core处理
IFormFile时,会将请求中的文件内容暂存到内存缓冲区(或磁盘临时文件)。即使你没有显式读取文件内容,框架为了完成IFormFile对象绑定,已经加载了请求体的文件部分到内存中。如果这些缓冲区的资源未被主动触发释放,GC不会立即回收这部分内存。 - Swagger测试工具的额外内存占用:Swagger在发送文件上传请求后,可能会缓存请求内容或记录请求元数据,频繁测试时这部分内存会持续累积,表现为内存无法释放。
- 框架内存池的复用机制:ASP.NET Core内部用于处理表单数据的
ArrayPool<byte>等内存池,短时间频繁处理文件请求时,会保留部分内存块以备复用,这属于正常机制,但会造成“内存泄漏”的假象。
解决方法
- 显式释放IFormFile的流资源:即使不需要读取文件内容,也主动打开并释放流,触发底层资源的清理:
[HttpPost()] [Route("UploadFile")] public async Task<IActionResult> UploadFileAsync(IFormFile uploadedFile) { // 显式打开并释放流,确保底层缓冲区被回收 await using var stream = uploadedFile.OpenReadStream(); return Ok(); } - 调整表单处理配置,减少内存缓存:在
Program.cs中配置表单上传的阈值,让超过大小的文件直接写入磁盘,避免内存占用:builder.Services.Configure<FormOptions>(options => { // 设置单次请求的最大表单长度 options.MultipartBodyLengthLimit = 10 * 1024 * 1024; // 10MB // 超过此阈值的文件部分写入磁盘临时文件,不再占用内存 options.MemoryBufferThreshold = 40 * 1024; // 40KB }); - 测试环境禁用Swagger请求缓存:修改Swagger配置,关闭请求历史记录或缓存,避免测试时的额外内存消耗:
builder.Services.AddSwaggerGen(options => { // 关闭Swagger的请求缓存相关功能(仅测试环境用) }); - 验证GC延迟回收(仅排查用):在接口末尾手动触发GC(生产环境禁止使用,会影响性能),确认是否为GC延迟导致的假泄漏:
[HttpPost()] [Route("UploadFile")] public async Task<IActionResult> UploadFileAsync(IFormFile uploadedFile) { await using var stream = uploadedFile.OpenReadStream(); GC.Collect(); GC.WaitForPendingFinalizers(); return Ok(); }
内容的提问来源于stack exchange,提问作者arn9000
相关产品推荐
相关产品推荐

