本文介绍如何管理已部署 ASP.NET 核心服务器端 Blazor 应用中的内存使用。
在服务器上,将为每个用户会话创建新线路。 每个用户会话对应于在浏览器中呈现单个文档。 例如,多个选项卡创建多个会话。
Blazor 维护与启动会话的浏览器(称为 线路)的常量连接。 由于多种原因(例如,用户失去网络连接或突然关闭浏览器)可以随时丢失连接。 连接丢失时, Blazor 有一种恢复机制,可将有限数量的线路置于“断开连接”池中,从而为客户端提供有限的时间重新连接并重新建立会话(默认值:3 分钟)。
之后,Blazor 释放电路并丢弃会话。 从那时起,电路有资格进行垃圾回收(GC),并在电路的 GC 代触发回收时完成回收。 要了解的一个重要方面是,线路的生存期很长,这意味着线路根系的大部分对象最终都达到第 2 代。 因此,您可能要等到发生 Gen 2 集合时,才会看到这些对象被释放。
总体衡量内存使用情况
先决条件:
- 应用必须在 发布 配置中发布。 调试 配置度量不相关,因为生成的代码不代表用于生产部署的代码。
- 应用必须在未附加调试器的情况下运行,因为这也可能会影响应用的行为并破坏结果。 在 Visual Studio 中,通过选择菜单栏中的 调试>开始执行但不调试 或键盘快捷键 Ctrl+F5 来启动应用程序而不进行调试。
- 请考虑不同类型的内存,以了解 .NET 实际使用的内存量。 通常,开发人员在 Windows OS 上的 Task Manager 中检查应用内存使用情况,这通常提供正在使用的实际内存的上限。 有关详细信息,请参阅以下文章:
- .NET 内存性能分析:特别是,请参阅 有关内存基础知识的部分。
- 诊断内存性能问题的工作流(三部分系列):本系列文章的三篇文章的链接位于该系列中的每个文章的顶部。
应用于Blazor的内存使用情况
我们计算 blazor 使用的内存,如下所示:
(活动线路 × 每线路内存) + (断开连接的线路 × 每线路内存)
线路使用的内存量和应用可以维护的最大潜在活动线路量在很大程度上取决于应用的写入方式。 可能的活动线路的最大数目大致如下所述:
最大可用内存 / 每线路内存 = 最大潜在活动线路数
若要在Blazor中发生内存泄漏,必须满足以下条件:
- 内存必须由框架而不是应用分配。 如果在应用中分配 1 GB 数组,应用必须管理该数组的处置。
- 内存不得主动使用,这意味着线路不处于活动状态,并且已从断开连接的线路缓存中逐出。 如果运行了最大活动电路,内存耗尽是一个扩展性问题,而不是内存泄漏。
- 线路的垃圾回收(GC)已运行,但垃圾回收器无法接管线路,因为框架中的另一个对象对线路保持强引用。
在其他情况下,没有内存泄漏。 如果线路处于活动状态(已连接或断开连接),则线路仍在使用中。
如果线路的 GC 生成集合未运行,则不会释放内存,因为垃圾回收器当时不需要释放内存。
如果 GC 生成集合运行并释放线路,则必须根据 GC 统计信息(而不是进程)验证内存,因为 .NET 可能会决定使虚拟内存保持活动状态。
如果未释放内存,则必须找到一条既不处于活动状态也未断开连接的线路,该线路根源于框架中的另一个对象。 在任何其他情况下,无法释放内存是开发人员代码中的应用问题。
减少内存使用量
采用以下任一策略来减少应用的内存使用量:
- 在发布配置中发布应用。
- 运行应用的已发布版本。
- 不要将调试器附加到正在运行的应用。
- 是否触发第 2 代强制压缩集合(
GC.Collect(2, GCCollectionMode.Aggressive | GCCollectionMode.Forced, blocking: true, compacting: true))释放内存? - 考虑你的应用是否正在分配大型对象堆上的对象。
- 在应用使用请求和处理进行热身后,是否测试内存增长? 通常,当代码首次执行时,会填充缓存,这些缓存会将一定数量的内存添加到应用的占用空间中。
- 限制 .NET 进程使用的内存总量。 有关详细信息,请参阅 垃圾回收的运行时配置选项。
- 减少断开连接的线路数。
- 缩短允许线路处于断开连接状态的时间。
- 手动触发垃圾回收,以在停机期间执行回收。
- 在工作站模式下配置垃圾回收,这会主动触发垃圾回收,而不是服务器模式。
某些移动设备浏览器的堆大小
构建一个在客户端上运行并针对移动设备浏览器(尤其是 iOS 上的 Safari)的应用时,可能需要使用 MSBuild 属性来减少应用的最大内存。 有关详细信息,请参阅托管和部署 ASP.NET Core Blazor WebAssembly。
附加措施和注意事项
- 当内存需求较高时,捕获进程的内存堆栈,并识别占用最多内存的对象,以及分析这些对象被引用的位置(即哪些引用持有这些对象)。
- 可以使用
dotnet-counters检查应用程序的内存使用情况统计信息。 有关详细信息,请参阅调查性能计数器(dotnet-counters)。 - 即使触发 GC,.NET 也会保留内存,而不是立即将其返回到 OS,因为它很可能在不久的将来重复使用内存。 这可以避免持续提交和取消提交内存,这很昂贵。 如果你使用
dotnet-counters,你将看到这一点反映,因为你会看到 GCS 发生,已用内存量下降到 0(零),但你不会看到工作集计数器减少,这是 .NET 保留内存以重复使用它的标志。 有关用于控制此行为的项目文件 (.csproj) 设置的详细信息,请参阅 垃圾回收的运行时配置选项。 - 服务器 GC 不会触发垃圾回收,除非确定绝对有必要这样做,以避免冻结应用,并认为应用是唯一在计算机上运行的事情,因此它可以使用系统中的所有内存。 如果系统具有 50 GB,垃圾回收器在触发第 2 代回收之前,会尝试使用完整的 50 GB 可用内存。
- 有关断开连接的线路保留配置的信息,请参阅 ASP.NET 核心 BlazorSignalR 指南。