filestream不关闭导致内存上涨的根本原因是未释放的safefilehandle阻止gc回收缓冲区和异步i/o对象;需用using包裹、避免fileshare.none长期持有、慎用readallbytes、memorycache须设过期策略与大小限制、禁用同步等待及async void、外部进程需显式管理。
为什么 FileStream 不关会导致内存占用持续上涨
根本原因不是文件句柄本身吃内存,而是未释放的 FileStream 持有底层 SafeFileHandle,进而阻止 GC 回收关联的缓冲区(尤其是启用了 bufferSize 且值较大时)和异步 I/O 状态对象。常见于用 using 包裹失败、或在异常路径中漏掉 Dispose()。
实操建议:
- 所有手动创建的
FileStream必须用using块包裹,哪怕只是读几行——别信“小文件无所谓” - 避免在构造函数里传
FileShare.None后长期持有实例;若需复用,改用File.OpenRead()/File.OpenWrite()并确保上层控制生命周期 - 检查是否误用了
File.ReadAllBytes()或File.ReadAllText()处理大文件:它们会把整个内容加载进托管堆,且无流式节制
MemoryCache 缓存文件内容却没设过期策略
很多人用 MemoryCache 存 byte[] 或 string 表示文件内容,但忘了默认不淘汰——缓存键不变,数据就永远留着,GC 不动它,内存只增不减。
实操建议:
- 显式设置
absoluteExpiration或slidingExpiration,哪怕只是TimeSpan.FromMinutes(5) - 对大文件内容,优先考虑缓存
FileInfo或哈希值,而非原始字节;真要缓存内容,加size配置项限制总容量(MemoryCacheOptions.SizeLimit) - 用
GetOrCreateAsync时注意:如果工厂方法抛异常,缓存不会写入,但调用方可能重试多次,导致重复加载——应在工厂内部做 try/catch + fallback
异步文件操作中 async/await 链断裂引发资源滞留
典型现象是 CPU 不高、线程数正常,但私有字节数(Private Bytes)缓慢爬升,尤其在 ASP.NET Core 中高频上传/下载场景。根源常是 await 后续丢失上下文,导致 FileStream 的终结器线程无法及时触发 Dispose,缓冲区卡在 finalizer queue。